'
143 | ~~~
144 |
145 | This occurs because you are using an old version of Jekyll located in `/usr/bin`.
146 | Make sure that you have installed Jekyll using:
147 |
148 | ~~~
149 | $ gem install jekyll
150 | ~~~
151 |
152 | This installs Jekyll in `/usr/local/bin`,
153 | so make sure this directory comes before `/usr/bin` in your `PATH` environment variable.
154 | When your path is set correctly,
155 | you should see:
156 |
157 | ~~~
158 | $ which jekyll
159 | /usr/local/bin/jekyll
160 | ~~~
161 |
162 | You may also have to install the `nodejs` package to disable references to JavaScript,
163 | which you can do using:
164 |
165 | ~~~
166 | $ sudo apt-get install nodejs
167 | ~~~
168 |
169 | For more information, see
170 | [http://michaelchelen.net/81fa/install-jekyll-2-ubuntu-14-04/](this article).
171 |
172 | * *Help, my website isn't rendering correctly once its pushed to GitHub!*
173 |
174 | This can occur if you're trying to view the website over a HTTPS connection
175 | because the content from the Software Carpentry website is not currently served over HTTPS.
176 | Your browser sees this as unsecure content, so won't load it.
177 |
178 | To solve this,
179 | use `http` in the URL instead of `https`
180 | and make sure the link you distribute does so as well.
181 | If you're using a browser plugin like [HTTPS Everywhere](https://www.eff.org/https-everywhere)
182 | you will need to disable it for your workshop's site.
183 | We are presently (January 2015) working to get HTTPS working properly on our website.
184 |
--------------------------------------------------------------------------------
/tools/test_check.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """Test suite for index page checking script."""
4 |
5 | from io import StringIO
6 | from datetime import date
7 | import check
8 |
9 | def test_check_layout():
10 | assert check.check_layout("workshop")
11 |
12 | def test_check_layout_fail():
13 | assert not check.check_layout("lesson")
14 |
15 | def test_check_root():
16 | assert check.check_root(".")
17 |
18 | def test_check_root_fail():
19 | assert not check.check_root("setup")
20 |
21 | def test_check_country_none():
22 | assert not check.check_country(None)
23 |
24 | def test_check_country_two_words():
25 | assert not check.check_country("Some Country")
26 |
27 | def test_check_country_abbrev():
28 | assert not check.check_country("USA")
29 |
30 | def test_check_country_correct_unhyphenated():
31 | assert check.check_country("Canada")
32 |
33 | def test_check_country_correct_hyphenated():
34 | assert check.check_country("United-Kingdom")
35 |
36 | def test_check_language_none():
37 | assert not check.check_layout(None)
38 |
39 | def test_check_language_name():
40 | assert not check.check_layout('english')
41 |
42 | def test_check_language_upper_name():
43 | assert not check.check_layout('English')
44 |
45 | def test_check_language_correct():
46 | assert check.check_language('en')
47 |
48 | def test_check_non_language_correct():
49 | assert not check.check_language('xx')
50 |
51 | def test_check_humandate():
52 | assert check.check_humandate("Feb 18-20, 2525")
53 |
54 | def test_check_humandate_fail():
55 | assert not check.check_humandate("February 18-20, 2525")
56 |
57 | def test_check_humandate_chars():
58 | assert not check.check_humandate("XXX SomeDay, Year")
59 |
60 | def test_check_humantime():
61 | assert not check.check_humantime("09:00am")
62 |
63 | def test_check_euro_humantime():
64 | assert check.check_humantime("09:00-17:00")
65 |
66 | def test_check_humantime_fail():
67 | assert not check.check_humantime("09:00")
68 |
69 | def test_check_humantime_only_am():
70 | assert not check.check_humantime("am")
71 |
72 | def test_check_humantime_without_spaces():
73 | assert check.check_humantime("9:00am-5:00pm")
74 |
75 | def test_check_humantime_with_spaces():
76 | assert check.check_humantime("9:00am - 5:00pm")
77 |
78 | def test_check_humantime_with_extra_spaces():
79 | assert check.check_humantime("9:00 am - 5:00 pm")
80 |
81 | def test_check_humantime_with_to():
82 | assert check.check_humantime("9:00am to 5:00pm")
83 |
84 | def test_check_humantime_with_to_and_spaces():
85 | assert check.check_humantime("9:00 am to 5:00 pm")
86 |
87 | def test_check_humantime_without_am_pm():
88 | assert check.check_humantime("9:00-17:00")
89 |
90 | def test_check_humantime_without_am_pm_with_to():
91 | assert check.check_humantime("9:00 to 17:00")
92 |
93 | def test_check_date():
94 | assert check.check_date(date(2525, 2, 20))
95 |
96 | def test_check_date_fail():
97 | assert not check.check_date("Feb 18-20, 2525")
98 |
99 | def test_check_latitude_longitude():
100 | assert check.check_latitude_longitude("0.0,0.0")
101 |
102 | def test_check_latitude_longitude_chars():
103 | assert not check.check_latitude_longitude("foo,bar")
104 |
105 | def test_check_instructors():
106 | assert check.check_instructors(["John Doe", "Jane Doe"])
107 |
108 | def test_check_instructor_only_one():
109 | assert check.check_instructors(["John Doe"])
110 |
111 | def test_check_instructor_empty():
112 | assert not check.check_instructors([])
113 |
114 | def test_check_instructor_string():
115 | assert not check.check_instructors("John Doe")
116 |
117 | def test_check_helpers():
118 | assert check.check_helpers(["John Doe", "Jane Doe"])
119 |
120 | def test_check_helpers_only_one():
121 | assert check.check_helpers(["John Doe"])
122 |
123 | def test_check_helpers_empty():
124 | assert check.check_helpers([])
125 |
126 | def test_check_helper_string():
127 | assert not check.check_helpers("John Doe")
128 |
129 | def test_check_email():
130 | assert check.check_email("user@box.com")
131 |
132 | def test_check_email_obfuscate():
133 | assert not check.check_email("user AT box DOT com")
134 |
135 | def test_check_email_not_default():
136 | assert not check.check_email('admin@software-carpentry.org')
137 |
138 | def test_check_eventbrite_9_digits():
139 | assert check.check_eventbrite('1' * 9)
140 |
141 | def test_check_eventbrite_10_digits():
142 | assert check.check_eventbrite('1' * 10)
143 |
144 | def test_check_not_eventbrite_8_digits():
145 | assert not check.check_eventbrite('1' * 8)
146 |
147 | def test_check_not_eventbrite_empty():
148 | assert not check.check_eventbrite('')
149 |
150 | def test_check_not_eventbrite_non_digits():
151 | assert not check.check_eventbrite('1' * 8 + 'a')
152 |
153 | def test_check_with_enddate():
154 | header = """---
155 | layout: workshop
156 | root: .
157 | venue: Euphoric State University
158 | address: 123 College Street, Euphoria
159 | country: United-States
160 | humandate: Feb 17-18, 2020
161 | humantime: 9:00 am - 4:30 pm
162 | startdate: 2020-06-17
163 | enddate: 2020-06-18
164 | latlng: 41.7901128,-87.6007318
165 | instructor: ["Grace Hopper", "Alan Turing"]
166 | helper: [ ]
167 | contact: alan@turing.com
168 | ---"""
169 |
170 | assert check.check_file('test.html', header) == []
171 |
172 | def test_check_without_enddate():
173 | header = """---
174 | layout: workshop
175 | root: .
176 | venue: Euphoric State University
177 | address: 123 College Street, Euphoria
178 | country: United-States
179 | humandate: Feb 17-18, 2020
180 | humantime: 9:00 am - 4:30 pm
181 | startdate: 2020-06-17
182 | latlng: 41.7901128,-87.6007318
183 | instructor: ["Grace Hopper", "Alan Turing"]
184 | contact: alan@turing.com
185 | helper: [ "John von Neumann" ]
186 | ---"""
187 |
188 | assert check.check_file('test.html', header) == []
189 |
190 | def test_check_with_blank_lines():
191 | header = """---
192 | layout: workshop
193 |
194 | root: .
195 |
196 | venue: Euphoric State University
197 | address: 123 College Street, Euphoria
198 | country: United-States
199 | humandate: Feb 17-18, 2020
200 | humantime: 9:00 am - 4:30 pm
201 | startdate: 2020-06-17
202 | enddate: 2020-06-18
203 | latlng: 41.7901128,-87.6007318
204 | instructor: ["Grace Hopper", "Alan Turing"]
205 | helper: [ ]
206 | contact: alan@turing.com
207 | ---"""
208 |
209 | assert check.check_file('test.html', header) != []
210 |
211 | def test_check_with_commented_lines():
212 | header = """---
213 | layout: workshop
214 | root: .
215 | venue: Euphoric State University
216 | address: 123 College Street, Euphoria
217 | country: United-States
218 | humandate: Feb 17-18, 2020
219 | humantime: 9:00 am - 4:30 pm
220 | startdate: 2020-06-17
221 | enddate: 2020-06-18
222 | latlng: 41.7901128,-87.6007318
223 | instructor: ["Grace Hopper", "Alan Turing"]
224 | helper: [ ]
225 | contact: alan@turing.com
226 | # eventbrite:
227 | ---"""
228 |
229 | assert check.check_file('test.html', header) == []
230 |
231 | def test_check_with_commented_values():
232 | header = """---
233 | layout: workshop
234 | root: .
235 | venue: Euphoric State University
236 | address: 123 College Street, Euphoria
237 | country: United-States
238 | humandate: Feb 17-18, 2020
239 | humantime: 9:00 am - 4:30 pm
240 | startdate: 2020-06-17
241 | enddate: 2020-06-18
242 | latlng: 41.7901128,-87.6007318
243 | instructor: ["Grace Hopper", "Alan Turing"]
244 | helper: [ ]
245 | contact: alan@turing.com
246 | eventbrite: # FIXME
247 | ---"""
248 |
249 | assert check.check_file('test.html', header) == []
250 |
251 | def test_check_with_leading_blank_lines():
252 | header = """
253 | ---
254 | layout: workshop
255 | root: .
256 | venue: Euphoric State University
257 | address: 123 College Street, Euphoria
258 | country: United-States
259 | humandate: Feb 17-18, 2020
260 | humantime: 9:00 am - 4:30 pm
261 | startdate: 2020-06-17
262 | enddate: 2020-06-18
263 | latlng: 41.7901128,-87.6007318
264 | instructor: ["Grace Hopper", "Alan Turing"]
265 | helper: [ ]
266 | contact: alan@turing.com
267 | eventbrite: # FIXME
268 | ---"""
269 |
270 | assert check.check_file('test.html', header) != []
271 |
272 | def test_check_with_missing_yaml_terminator():
273 | header = """
274 | ---
275 | layout: workshop
276 | root: .
277 |
282 | """
283 |
284 | assert check.check_file('test.html', header) != []
285 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # workshop-template
2 |
3 | This repository is [Software Carpentry](http://software-carpentry.org)'s
4 | template for creating websites for workshops.
5 |
6 | 1. Do *not* fork this repository directly on GitHub.
7 | Instead, please use GitHub's importer following the instructions [below](#creating-a-repository)
8 | to create a website repository for your workshop.
9 |
10 | 2. Please *do your work in your repository's `gh-pages` branch*,
11 | since that is what is [automatically published as a website by GitHub](https://help.github.com/articles/creating-project-pages-manually/).
12 |
13 | 3. Once you are done,
14 | please **send your repository's URL to the [Software Carpentry administrator](mailto:admin@software-carpentry.org)**.
15 | We build the [list of workshops on the main website](http://software-carpentry.org/workshops/index.html)
16 | from the data included in your `index.html` page.
17 | We can only do that if you [customize](CUSTOMIZATION.md) that page correctly
18 | *and* send us a link to your workshop website.
19 |
20 | 4. Please also read
21 | [the notes on customizing your website](CUSTOMIZATION.md) and the [FAQ](FAQ.md).
22 | If you're interested in knowing more about why we do things the way we do,
23 | please check out the [design notes](DESIGN.md).
24 |
25 | 5. If you are teaching Git,
26 | please [create a separate repository](#setting-up-a-separate-repository-for-learners)
27 | for your learners to practice in.
28 |
29 | 6. If you run into problems,
30 | or have ideas about how to make this process simpler,
31 | please [get in touch](#getting-and-giving-help).
32 |
33 | ## Creating a Repository
34 |
35 | 1. Go to [GitHub's importer][import].
36 |
37 | 2. Click on "Check the URL". (GitHub won't import until you've done this.)
38 |
39 | 3. Select the owner for your new repository.
40 | (This will probably be you, but may instead be an organization you belong to.)
41 |
42 | 4. Choose a name for your workshop website repository.
43 | This name should have the form `YYYY-MM-DD-site`,
44 | e.g., `2015-07-01-miskatonic`.
45 |
46 | 5. Make sure the repository is public.
47 |
48 | 6. At this point, you should have a page like this:
49 |
50 | 
51 |
52 | You can now click "Begin Import".
53 | When the process is done,
54 | you can click "Continue to repository" to visit your newly-created repository.
55 |
56 | **Note:**
57 | some people have had intermittent errors during the import process,
58 | possibly because of the network timing out.
59 | If you experience a problem, please re-try;
60 | if the problem persists,
61 | please [get in touch](#getting-and-giving-help).
62 |
63 | To clone your new repository, use:
64 |
65 | ~~~
66 | git clone -b gh-pages https://github.com/your_username/YYYY-MM-DD-site
67 | ~~~
68 |
69 | This is needed because the imported repository doesn't have a `master` branch.
70 |
71 | **Note:** please do all of your work in your repository's `gh-pages` branch,
72 | since [GitHub automatically publishes that as a website](https://help.github.com/articles/creating-project-pages-manually/).
73 |
74 | ## Customizing Your Website
75 |
76 | 1. Go into your newly-created repository,
77 | which will be at `https://github.com/your_username/YYYY-MM-DD-site`.
78 | For example,
79 | if `your_username` is `gvwilson`,
80 | the repository's URL will be `https://github.com/gvwilson/2015-07-01-mistaktonic`.
81 |
82 | 2. Edit `index.html` to customize the list of instructors,
83 | workshop venue,
84 | etc.
85 | You can do this in the browser by clicking on it in the file view
86 | and then selecting the pencil icon in the menu bar:
87 |
88 | 
89 |
90 | or you can clone the repository to your desktop,
91 | edit `index.html` there,
92 | and push your changes back to the repository.
93 | Editing hints are embedded in `index.html`,
94 | and full instructions are in [CUSTOMIZATION.md](CUSTOMIZATION.md).
95 |
96 | 3. Edit `_config.yml` in the same way
97 | so that `workshop_repo` and `workshop_site`
98 | are the URLs of your repository and your GitHub Pages website respectively.
99 |
100 | Note: the URL for your website is determined automatically
101 | based on the URL for your repository.
102 | If your repository is at `https://github.com/gvwilson/2015-07-01-mistaktonic`,
103 | its GitHub Pages website is at `http://gvwilson.github.io/2015-07-01-miskatonic`.
104 |
105 | 4. When you are done editing,
106 | you can preview your website.
107 | Again,
108 | if your repository is `https://github.com/your_username/YYYY-MM-DD-site`,
109 | its website will be `http://your_username.github.io/YYYY-MM-DD-site`.
110 |
111 | Full instructions are available in [CUSTOMIZATION.md](CUSTOMIZATION.md).
112 | This [FAQ](FAQ.md) includes a few extra tips
113 | (additions are always welcome)
114 | and these notes on [the background and design](DESIGN.md) of this template may help as well.
115 |
116 | That's it.
117 | The following steps are only necessary if you want to run the website locally on your computer.
118 |
119 | ## Checking Your Changes
120 |
121 | **Note:** to check your changes you need some softwares
122 | that are describe at [Installing Software session](#installing-software).
123 |
124 | No matter how you edit `index.html`, you should:
125 |
126 | 1. Check your changes by running `tools/check.py` at the command line
127 | from the root directory of your repository.
128 |
129 | 2. Preview your changes by running `tools/preview` and looking at `_site/index.html`.
130 |
131 | For some links to work properly,
132 | particularly the link to your workshop's Eventbrite registration page,
133 | you must view `_site/index.html` using an HTTP server.
134 | If you have Jekyll installed,
135 | you can do this by running:
136 |
137 | ~~~
138 | $ jekyll server -d _site
139 | ~~~
140 |
141 | and going to http://localhost:4000.
142 |
143 | ## Installing Software
144 |
145 | In order to preview the workshop website locally on your computer,
146 | you must install the software described below.
147 |
148 | > If you aren't able to install this software (or you just can't be
149 | > bothered), you can still create a website for your workshop. Every
150 | > time you push a change to your website respository the live website
151 | > will update automatically, so you can check your changes on the live
152 | > site instead of locally.
153 |
154 | 1. Ruby 2.0 or greater
155 |
156 | On Debian/Ubuntu based machines you can install it using
157 |
158 | ~~~
159 | $ sudo apt-get install ruby2.0 ruby2.0-dev
160 | ~~~
161 |
162 | 2. NodeJS
163 |
164 | On Debian/Ubuntu based machines you can install it using
165 |
166 | ~~~
167 | $ sudo apt-get install nodejs
168 | ~~~
169 |
170 | 3. Jekyll
171 |
172 | Install `github-pages`:
173 |
174 | ~~~
175 | $ gem install github-pages
176 | ~~~
177 |
178 | or if that doesn't work:
179 |
180 | ~~~
181 | $ gem install jekyll
182 | $ gem install kramdown
183 | ~~~
184 |
185 | We use Kramdown to translate Markdown into HTML, instead of
186 | the default Redcarpet, because Kramdown handles Markdown
187 | inside HTML blocks.
188 |
189 | 2. The Python YAML module
190 |
191 | If you are using the Anaconda Python distribution, you probably
192 | already have it; if you don't, you can install it with:
193 |
194 | ~~~
195 | $ conda install pyyaml
196 | ~~~
197 |
198 | If you are using some other distribution, you can install the
199 | Python YAML module using Pip:
200 |
201 | ~~~
202 | $ pip install pyyaml
203 | ~~~
204 |
205 | and if you are on Debian Linux, you can use:
206 |
207 | ~~~
208 | $ apt-get install python-yaml
209 | ~~~
210 |
211 | ## Setting Up a Separate Repository for Learners
212 |
213 | If you are teaching Git,
214 | you should create a separate repository for learners to use in that lesson.
215 | You should not have them use the workshop website repository because:
216 |
217 | * your workshop website repository contains many files
218 | that most learners don't need to see during the lesson,
219 | and
220 |
221 | * you probably don't want to accidentally merge
222 | a damaging pull request from a novice Git user
223 | into your workshop's website while you are using it to teach.
224 |
225 | You can call this repository whatever you like,
226 | and add whatever content you need to it.
227 |
228 | ## Getting and Giving Help
229 |
230 | We are committed to offering a pleasant setup experience for our learners and organizers.
231 | If you find bugs in our instructions,
232 | or would like to suggest improvements,
233 | please [file an issue](https://github.com/swcarpentry/workshop-template/issues)
234 | or [mail us](mailto:admin@software-carpentry.org).
235 |
236 | [import]: http://import.github.com/new?import_url=https://github.com/swcarpentry/workshop-template
237 |
--------------------------------------------------------------------------------
/css/slideshow.css:
--------------------------------------------------------------------------------
1 | /* This theme is generated by deck.js-theme-builder. */
2 | /* https://github.com/twitwi/deck.js-theme-builder */
3 | /* Resets and base styles from HTML5 Boilerplate */
4 | div, span, object, iframe,
5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
6 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
7 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, figcaption, figure,
11 | footer, header, hgroup, menu, nav, section, summary,
12 | time, mark, audio, video {
13 | margin: 0;
14 | padding: 0;
15 | border: 0;
16 | font-size: 100%;
17 | font: inherit;
18 | vertical-align: baseline;
19 | }
20 |
21 | article, aside, details, figcaption, figure,
22 | footer, header, hgroup, menu, nav, section {
23 | display: block;
24 | }
25 |
26 | blockquote, q {
27 | quotes: none;
28 | }
29 | blockquote:before, blockquote:after, q:before, q:after {
30 | content: "";
31 | content: none;
32 | }
33 |
34 | ins {
35 | background-color: #ff9;
36 | color: #000;
37 | text-decoration: none;
38 | }
39 |
40 | mark {
41 | background-color: #ff9;
42 | color: #000;
43 | font-style: italic;
44 | font-weight: bold;
45 | }
46 |
47 | del {
48 | text-decoration: line-through;
49 | }
50 |
51 | abbr[title], dfn[title] {
52 | border-bottom: 1px dotted;
53 | cursor: help;
54 | }
55 |
56 | table {
57 | border-collapse: collapse;
58 | border-spacing: 0;
59 | }
60 |
61 | hr {
62 | display: block;
63 | height: 1px;
64 | border: 0;
65 | border-top: 1px solid #ccc;
66 | margin: 1em 0;
67 | padding: 0;
68 | }
69 |
70 | input, select {
71 | vertical-align: middle;
72 | }
73 |
74 | select, input, textarea, button {
75 | font: 99% sans-serif;
76 | }
77 |
78 | pre, code, kbd, samp {
79 | font-family: monospace, sans-serif;
80 | }
81 |
82 | a {
83 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
84 | }
85 | a:hover, a:active {
86 | outline: none;
87 | }
88 |
89 | ul, ol {
90 | margin-left: 2em;
91 | vertical-align: top;
92 | }
93 |
94 | ol {
95 | list-style-type: decimal;
96 | }
97 |
98 | nav ul, nav li {
99 | margin: 0;
100 | list-style: none;
101 | list-style-image: none;
102 | }
103 |
104 | small {
105 | font-size: 85%;
106 | }
107 |
108 | strong, th {
109 | font-weight: bold;
110 | }
111 |
112 | td {
113 | vertical-align: top;
114 | }
115 |
116 | sub, sup {
117 | font-size: 75%;
118 | line-height: 0;
119 | position: relative;
120 | }
121 |
122 | sup {
123 | top: -0.5em;
124 | }
125 |
126 | sub {
127 | bottom: -0.25em;
128 | }
129 |
130 | textarea {
131 | overflow: auto;
132 | }
133 |
134 | input[type="radio"] {
135 | vertical-align: text-bottom;
136 | }
137 |
138 | input[type="checkbox"] {
139 | vertical-align: bottom;
140 | }
141 |
142 | label,
143 | input[type="button"],
144 | input[type="submit"],
145 | input[type="image"],
146 | button {
147 | cursor: pointer;
148 | }
149 |
150 | button, input, select, textarea {
151 | margin: 0;
152 | }
153 |
154 | input:invalid, textarea:invalid {
155 | border-radius: 1px;
156 | -moz-box-shadow: 0px 0px 5px red;
157 | -webkit-box-shadow: 0px 0px 5px red;
158 | box-shadow: 0px 0px 5px red;
159 | }
160 | input:invalid .no-boxshadow, textarea:invalid .no-boxshadow {
161 | background-color: #f0dddd;
162 | }
163 |
164 | button {
165 | width: auto;
166 | overflow: visible;
167 | }
168 |
169 | select, input, textarea {
170 | color: #444444;
171 | }
172 |
173 | a {
174 | color: #607890;
175 | }
176 | a:hover, a:focus {
177 | color: #036;
178 | }
179 | a:link {
180 | -webkit-tap-highlight-color: #fff;
181 | }
182 |
183 | /* End HTML5 Boilerplate adaptations */
184 | h1 {
185 | font-size: 4.5em;
186 | }
187 |
188 | h1, .vcenter {
189 | font-weight: bold;
190 | text-align: center;
191 | padding-top: 1em;
192 | max-height: 100%;
193 | }
194 | .csstransforms h1, .csstransforms .vcenter {
195 | padding: 0 48px;
196 | position: absolute;
197 | left: 0;
198 | right: 0;
199 | top: 50%;
200 | -webkit-transform: translate(0, -50%);
201 | -moz-transform: translate(0, -50%);
202 | -ms-transform: translate(0, -50%);
203 | -o-transform: translate(0, -50%);
204 | transform: translate(0, -50%);
205 | }
206 |
207 | .vcenter h1 {
208 | position: relative;
209 | top: auto;
210 | padding: 0;
211 | -webkit-transform: none;
212 | -moz-transform: none;
213 | -ms-transform: none;
214 | -o-transform: none;
215 | transform: none;
216 | }
217 |
218 | h2 {
219 | font-size: 2.25em;
220 | font-weight: bold;
221 | padding-top: .5em;
222 | margin: 0 0 .66666em 0;
223 | border-bottom: 3px solid #888;
224 | }
225 |
226 | h3 {
227 | font-size: 1.4375em;
228 | font-weight: bold;
229 | margin-bottom: .30435em;
230 | }
231 |
232 | h4 {
233 | font-size: 1.25em;
234 | font-weight: bold;
235 | margin-bottom: .25em;
236 | }
237 |
238 | h5 {
239 | font-size: 1.125em;
240 | font-weight: bold;
241 | margin-bottom: .2222em;
242 | }
243 |
244 | h6 {
245 | font-size: 1em;
246 | font-weight: bold;
247 | }
248 |
249 | img, iframe, video {
250 | display: block;
251 | max-width: 100%;
252 | }
253 |
254 | video, iframe, img {
255 | display: block;
256 | margin: 0 auto;
257 | }
258 |
259 | p, blockquote, iframe, img, ul, ol, pre, video {
260 | margin-bottom: 1em;
261 | }
262 |
263 | pre {
264 | white-space: pre;
265 | white-space: pre-wrap;
266 | word-wrap: break-word;
267 | padding: 1em;
268 | border: 1px solid #888;
269 | }
270 |
271 | em {
272 | font-style: italic;
273 | }
274 |
275 | li {
276 | padding: .25em 0;
277 | vertical-align: middle;
278 | }
279 | li > ol, li > ul {
280 | margin-bottom: inherit;
281 | }
282 |
283 | .deck-container {
284 | font-size: 16px;
285 | line-height: 1.25;
286 | color: #444;
287 | }
288 |
289 | .slide {
290 | -moz-box-sizing: border-box;
291 | box-sizing: border-box;
292 | width: 100%;
293 | }
294 |
295 | /* colors */
296 | /* extras, all together for now */
297 | @font-face {
298 | font-family: 'Montserrat';
299 | font-style: normal;
300 | font-weight: 400;
301 | src: local("Montserrat-Regular"), url(local-fonts/zhcz-_WihjSQC0oHJ9TCYBsxEYwM7FgeyaSgU71cLG0.woff) format("woff"), url(http://fonts.gstatic.com/s/montserrat/v6/zhcz-_WihjSQC0oHJ9TCYBsxEYwM7FgeyaSgU71cLG0.woff) format("woff");
302 | }
303 |
304 | .timekeeper {
305 | font-family: monospace;
306 | }
307 |
308 | .deck-container {
309 | font-family: 'Montserrat', 'Arial', 'Sans';
310 | }
311 |
312 | /* sizes */
313 | .deck-container {
314 | font-size: 30px;
315 | }
316 |
317 | h1 {
318 | font-size: 1.75em;
319 | }
320 |
321 | h2 {
322 | margin: 0;
323 | padding: 0 10px;
324 | font-size: 1.5em;
325 | }
326 |
327 | .deck-container > .slide {
328 | padding-top: 3em;
329 | }
330 |
331 | h2 {
332 | position: absolute;
333 | top: 0;
334 | left: 0;
335 | width: 100%;
336 | }
337 |
338 | .deck-container > .slide.noh2, .deck-container > .slide.noH2 {
339 | padding-top: 0;
340 | }
341 |
342 | h2 {
343 | text-align: center;
344 | }
345 |
346 | .deck-container > .slide > ul > li ul, .deck-container > .slide > ul > li ol, .deck-container > .slide > ol > li ul, .deck-container > .slide > ol > li ol {
347 | line-height: 0.85em;
348 | margin-bottom: 0px;
349 | }
350 | .deck-container > .slide > ul > li ul > li ul, .deck-container > .slide > ul > li ul > li ol, .deck-container > .slide > ul > li ol > li ul, .deck-container > .slide > ul > li ol > li ol, .deck-container > .slide > ol > li ul > li ul, .deck-container > .slide > ol > li ul > li ol, .deck-container > .slide > ol > li ol > li ul, .deck-container > .slide > ol > li ol > li ol {
351 | margin-bottom: 0px;
352 | }
353 | .deck-container > .slide > ul > li ul > li ul > li, .deck-container > .slide > ul > li ul > li ol > li, .deck-container > .slide > ul > li ol > li ul > li, .deck-container > .slide > ul > li ol > li ol > li, .deck-container > .slide > ol > li ul > li ul > li, .deck-container > .slide > ol > li ul > li ol > li, .deck-container > .slide > ol > li ol > li ul > li, .deck-container > .slide > ol > li ol > li ol > li {
354 | margin: 0.2em;
355 | font-size: 0.8em;
356 | margin-bottom: 0px;
357 | }
358 | .deck-container > .slide > ul > li ul > li, .deck-container > .slide > ul > li ol > li, .deck-container > .slide > ol > li ul > li, .deck-container > .slide > ol > li ol > li {
359 | margin: 0.2em;
360 | font-size: 0.85em;
361 | margin-bottom: 0px;
362 | }
363 | .deck-container > .slide > ul li, .deck-container > .slide > ol li {
364 | margin: 0;
365 | padding: 0;
366 | margin-top: 0.5em;
367 | padding-left: 0em;
368 | }
369 | .deck-container > .slide > ul {
370 | list-style: disc outside none;
371 | }
372 | .deck-container > .slide > ul > li ul {
373 | list-style: square outside none;
374 | }
375 |
376 | blockquote {
377 | font-size: 2em;
378 | font-style: italic;
379 | padding: 1em 2em;
380 | color: #000;
381 | border-left: 5px solid #ccc;
382 | }
383 | blockquote p {
384 | margin: 0;
385 | }
386 | blockquote cite {
387 | font-size: .5em;
388 | font-style: normal;
389 | font-weight: bold;
390 | color: #888;
391 | }
392 |
393 | a, a:hover, a:focus, a:active, a:visited {
394 | text-decoration: none;
395 | }
396 | a:hover, a:focus {
397 | text-decoration: underline;
398 | }
399 |
400 | .deck-prev-link, .deck-next-link {
401 | background: #ccc;
402 | font-family: serif;
403 | }
404 | .deck-prev-link, .deck-prev-link:hover, .deck-prev-link:focus, .deck-prev-link:active, .deck-prev-link:visited, .deck-next-link, .deck-next-link:hover, .deck-next-link:focus, .deck-next-link:active, .deck-next-link:visited {
405 | color: #fff;
406 | }
407 | .deck-prev-link:hover, .deck-prev-link:focus, .deck-next-link:hover, .deck-next-link:focus {
408 | background: #c00;
409 | text-decoration: none;
410 | }
411 |
412 | .deck-menu > .slide {
413 | /* matching toplevel slides... ideally, we would expect them to have an additional class */
414 | background: #eee;
415 | }
416 | .deck-menu > .deck-current, .deck-menu > .deck-child-current {
417 | outline-offset: 20px;
418 | outline: 10px solid #f88;
419 | }
420 | .no-touch .deck-menu .slide:hover {
421 | outline-offset: 20px;
422 | outline: 10px solid red;
423 | background: #ddf;
424 | }
425 |
426 | .deck-container {
427 | background: white;
428 | color: #1f2969;
429 | font-weight: normal;
430 | }
431 | .deck-container:not(.no-status) > .slide {
432 | background: url(../img/software-carpentry-banner.png) bottom 10px left 10px no-repeat;
433 | background-size: 150px;
434 | }
435 | .deck-container .slide :not(h1):not(h2) {
436 | /* TODO: the original style mispelled the font name, so it was using Helvetica for everything but the titles, this line does the same. */
437 | font-family: 'Helvetica', 'Arial', 'sans';
438 | }
439 |
440 | .deck-container .slide {
441 | margin: 0;
442 | padding: 160px 50px;
443 | /* text-align: justify;*/
444 | }
445 |
446 | .slide.no-bullets ol, .slide.no-bullets ul {
447 | list-style: none;
448 | }
449 |
450 | .slide .center {
451 | text-align: center;
452 | }
453 |
454 | .slide .left {
455 | float: left;
456 | width: 50%;
457 | }
458 |
459 | .slide .right {
460 | float: left;
461 | width: 50%;
462 | }
463 |
464 | .slide .c1 {
465 | width: 8.33333% !important;
466 | }
467 |
468 | .slide .c2 {
469 | width: 16.66667% !important;
470 | }
471 |
472 | .slide .c3 {
473 | width: 25% !important;
474 | }
475 |
476 | .slide .c4 {
477 | width: 33.33333% !important;
478 | }
479 |
480 | .slide .c5 {
481 | width: 41.66667% !important;
482 | }
483 |
484 | .slide .c6 {
485 | width: 50% !important;
486 | }
487 |
488 | .slide .c7 {
489 | width: 58.33333% !important;
490 | }
491 |
492 | .slide .c8 {
493 | width: 66.66667% !important;
494 | }
495 |
496 | .slide .c9 {
497 | width: 75% !important;
498 | }
499 |
500 | .slide .c10 {
501 | width: 83.33333% !important;
502 | }
503 |
504 | .slide .c11 {
505 | width: 91.66667% !important;
506 | }
507 |
508 | .slide .c12 {
509 | width: 100% !important;
510 | }
511 |
512 | .slide .C1 {
513 | width: 4.16667% !important;
514 | }
515 |
516 | .slide .C2 {
517 | width: 8.33333% !important;
518 | }
519 |
520 | .slide .C3 {
521 | width: 12.5% !important;
522 | }
523 |
524 | .slide .C4 {
525 | width: 16.66667% !important;
526 | }
527 |
528 | .slide .C5 {
529 | width: 20.83333% !important;
530 | }
531 |
532 | .slide .C6 {
533 | width: 25% !important;
534 | }
535 |
536 | .slide .C7 {
537 | width: 29.16667% !important;
538 | }
539 |
540 | .slide .C8 {
541 | width: 33.33333% !important;
542 | }
543 |
544 | .slide .C9 {
545 | width: 37.5% !important;
546 | }
547 |
548 | .slide .C10 {
549 | width: 41.66667% !important;
550 | }
551 |
552 | .slide .C11 {
553 | width: 45.83333% !important;
554 | }
555 |
556 | .slide .C12 {
557 | width: 50% !important;
558 | }
559 |
560 | .slide .C13 {
561 | width: 54.16667% !important;
562 | }
563 |
564 | .slide .C14 {
565 | width: 58.33333% !important;
566 | }
567 |
568 | .slide .C15 {
569 | width: 62.5% !important;
570 | }
571 |
572 | .slide .C16 {
573 | width: 66.66667% !important;
574 | }
575 |
576 | .slide .C17 {
577 | width: 70.83333% !important;
578 | }
579 |
580 | .slide .C18 {
581 | width: 75% !important;
582 | }
583 |
584 | .slide .C19 {
585 | width: 79.16667% !important;
586 | }
587 |
588 | .slide .C20 {
589 | width: 83.33333% !important;
590 | }
591 |
592 | .slide .C21 {
593 | width: 87.5% !important;
594 | }
595 |
596 | .slide .C22 {
597 | width: 91.66667% !important;
598 | }
599 |
600 | .slide .C23 {
601 | width: 95.83333% !important;
602 | }
603 |
604 | .slide .C24 {
605 | width: 100% !important;
606 | }
607 |
608 | .slide .clearboth {
609 | clear: both;
610 | }
611 |
612 | .slide .right {
613 | float: right;
614 | width: 50%;
615 | }
616 |
617 | div.figure p.caption {
618 | color: #141a41;
619 | text-align: center;
620 | font-size: 80%;
621 | }
622 |
623 | .slide .darker {
624 | color: #141a41;
625 | }
626 |
627 | .slide.media-left > *:not(h2):not(img):not(blockquote):not(pre):not(div), .slide.media-left > div:not(.figure) {
628 | margin-left: 400px;
629 | }
630 | .slide.media-left > div.figure, .slide.media-left > img, .slide.media-left > blockquote, .slide.media-left > pre {
631 | float: left;
632 | width: 50%;
633 | box-sizing: border-box;
634 | }
635 |
636 | .slide.media-right > *:not(h2):not(img):not(blockquote):not(pre):not(div), .slide.media-right > div:not(.figure) {
637 | margin-right: 400px;
638 | }
639 | .slide.media-right > div.figure, .slide.media-right > img, .slide.media-right > blockquote, .slide.media-right > pre {
640 | float: right;
641 | width: 50%;
642 | box-sizing: border-box;
643 | }
644 |
645 | .slide.image-stripes > div {
646 | overflow: hidden;
647 | align-content: center;
648 | }
649 | .slide.image-stripes img {
650 | height: 400px;
651 | max-width: none;
652 | position: relative;
653 | left: 50%;
654 | transform: translate(-50%, 0);
655 | }
656 |
657 | h1, h2 {
658 | color: #2b3990;
659 | font-size: 70px;
660 | line-height: 1em;
661 | font-weight: normal;
662 | }
663 |
664 | h2 {
665 | border-bottom: none;
666 | font-size: 60px;
667 | position: absolute;
668 | left: 0;
669 | right: 0;
670 | bottom: 480px;
671 | top: auto;
672 | }
673 |
674 | h3 {
675 | color: #888;
676 | }
677 |
678 | pre {
679 | border-color: #ccc;
680 | }
681 |
682 | code {
683 | color: #555;
684 | }
685 |
686 | blockquote {
687 | font-size: 20px;
688 | }
689 |
690 | a {
691 | transition: color 200ms;
692 | }
693 | a, a:focus, a:active, a:visited {
694 | color: #1f2969;
695 | }
696 | a:hover {
697 | color: #6877d2;
698 | text-decoration: none;
699 | }
700 |
701 | .slide.title-slide {
702 | text-align: center;
703 | padding-top: 325px;
704 | }
705 | .slide.title-slide h1 {
706 | padding-bottom: 50px;
707 | bottom: 50%;
708 | top: auto;
709 | height: auto;
710 | transform: none;
711 | }
712 | .slide.title-slide img.logo {
713 | position: absolute;
714 | padding-top: 100px;
715 | left: 0;
716 | right: 0;
717 | top: 50%;
718 | align: center;
719 | }
720 |
721 | .progress-bar {
722 | background: #2b3990;
723 | opacity: 0.5;
724 | transition: width 500ms;
725 | }
726 |
727 | /* #20267b? */
728 |
--------------------------------------------------------------------------------
/tools/check.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''Check that index.html is valid and print out warnings and errors
4 | when the header is malformed. See the docstrings on the checking
5 | functions for a summary of the checks.
6 | '''
7 |
8 | from __future__ import print_function
9 | import sys
10 | import os
11 | import re
12 | import logging
13 | import yaml
14 | from collections import Counter
15 |
16 | try: # Hack to make codebase compatible with python 2 and 3
17 | our_basestring = basestring
18 | except NameError:
19 | our_basestring = str
20 |
21 | __version__ = '0.6'
22 |
23 |
24 | # basic logging configuration
25 | logger = logging.getLogger(__name__)
26 | verbosity = logging.INFO # severity of at least INFO will emerge
27 | logger.setLevel(verbosity)
28 |
29 | # create console handler and set level to debug
30 | console_handler = logging.StreamHandler()
31 | console_handler.setLevel(verbosity)
32 |
33 | formatter = logging.Formatter('%(levelname)s: %(message)s')
34 | console_handler.setFormatter(formatter)
35 | logger.addHandler(console_handler)
36 |
37 |
38 | # TODO: these regexp patterns need comments inside
39 | EMAIL_PATTERN = r'[^@]+@[^@]+\.[^@]+'
40 | 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)'
41 | EVENTBRITE_PATTERN = r'\d{9,10}'
42 | URL_PATTERN = r'https?://.+'
43 |
44 | DEFAULT_CONTACT_EMAIL = 'admin@software-carpentry.org'
45 |
46 | USAGE = 'Usage: "python check.py" or "python check.py path/to/index.html"\n'
47 |
48 | # Country and language codes. Note that codes mean different things: 'ar'
49 | # is 'Arabic' as a language but 'Argentina' as a country.
50 |
51 | ISO_COUNTRY = [
52 | 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'as',
53 | 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
54 | 'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz',
55 | 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co',
56 | 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz',
57 | 'ec', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm',
58 | 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm',
59 | 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn',
60 | 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is',
61 | 'it', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp',
62 | 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt',
63 | 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm',
64 | 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my',
65 | 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu',
66 | 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr',
67 | 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb',
68 | 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so',
69 | 'sr', 'st', 'sv', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk',
70 | 'tl', 'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'um',
71 | 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws',
72 | 'ye', 'yt', 'za', 'zm', 'zw'
73 | ]
74 |
75 | ISO_LANGUAGE = [
76 | 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
77 | 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
78 | 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
79 | 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
80 | 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
81 | 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',
82 | 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',
83 | 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',
84 | 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms',
85 | 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv',
86 | 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu',
87 | 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk',
88 | 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta',
89 | 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw',
90 | 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi',
91 | 'yo', 'za', 'zh', 'zu'
92 | ]
93 |
94 |
95 | def add_error(msg, errors):
96 | """Add error to the list of errors."""
97 | errors.append(msg)
98 |
99 |
100 | def add_suberror(msg, errors):
101 | """Add sub error, ie. error indented by 1 level ("\t"), to the list of errors."""
102 | errors.append("\t{0}".format(msg))
103 |
104 |
105 | def look_for_fixme(func):
106 | '''Decorator to fail test if text argument starts with "FIXME".'''
107 | def inner(arg):
108 | if (arg is not None) and \
109 | isinstance(arg, our_basestring) and \
110 | arg.lstrip().startswith('FIXME'):
111 | return False
112 | return func(arg)
113 | return inner
114 |
115 |
116 | @look_for_fixme
117 | def check_layout(layout):
118 | '''"layout" in YAML header must be "workshop".'''
119 |
120 | return layout == 'workshop'
121 |
122 |
123 | @look_for_fixme
124 | def check_root(root):
125 | '''"root" (the path from this page to the root directory) must be "."'''
126 |
127 | return root == '.'
128 |
129 |
130 | @look_for_fixme
131 | def check_country(country):
132 | '''"country" must be an ISO-3166 two-letter code.'''
133 |
134 | return country in ISO_COUNTRY
135 |
136 |
137 | @look_for_fixme
138 | def check_language(language):
139 | '''"language" must be an ISO-639 two-letter code.'''
140 |
141 | return language in ISO_LANGUAGE
142 |
143 |
144 | @look_for_fixme
145 | def check_humandate(date):
146 | '''"humandate" must be a human-readable date with a 3-letter month and
147 | 4-digit year. Examples include "Feb 18-20, 2025" and "Feb 18 and
148 | 20, 2025". It may be in languages other than English, but the
149 | month name should be kept short to aid formatting of the main
150 | Software Carpentry web site.'''
151 |
152 | if "," not in date:
153 | return False
154 |
155 | month_dates, year = date.split(",")
156 |
157 | # The first three characters of month_dates are not empty
158 | month = month_dates[:3]
159 | if any(char == " " for char in month):
160 | return False
161 |
162 | # But the fourth character is empty ("February" is illegal)
163 | if month_dates[3] != " ":
164 | return False
165 |
166 | # year contains *only* numbers
167 | try:
168 | int(year)
169 | except:
170 | return False
171 |
172 | return True
173 |
174 |
175 | @look_for_fixme
176 | def check_humantime(time):
177 | '''"humantime" is a human-readable start and end time for the workshop,
178 | such as "09:00 - 16:00".'''
179 |
180 | return bool(re.match(HUMANTIME_PATTERN, time.replace(" ", "")))
181 |
182 |
183 | def check_date(this_date):
184 | '''"startdate" and "enddate" are machine-readable start and end dates for
185 | the workshop, and must be in YYYY-MM-DD format, e.g., "2015-07-01".'''
186 |
187 | from datetime import date
188 | # yaml automatically loads valid dates as datetime.date
189 | return isinstance(this_date, date)
190 |
191 |
192 | @look_for_fixme
193 | def check_latitude_longitude(latlng):
194 | '''"latlng" must be a valid latitude and longitude represented as two
195 | floating-point numbers separated by a comma.'''
196 |
197 | try:
198 | lat, lng = latlng.split(',')
199 | lat = float(lat)
200 | long = float(lng)
201 | except ValueError:
202 | return False
203 | return (-90.0 <= lat <= 90.0) and (-180.0 <= long <= 180.0)
204 |
205 |
206 | def check_instructors(instructors):
207 | '''"instructor" must be a non-empty comma-separated list of quoted names,
208 | e.g. ['First name', 'Second name', ...']. Do not use "TBD" or other
209 | placeholders.'''
210 |
211 | # yaml automatically loads list-like strings as lists
212 | return isinstance(instructors, list) and len(instructors) > 0
213 |
214 |
215 | def check_helpers(helpers):
216 | '''"helper" must be a comma-separated list of quoted names,
217 | e.g. ['First name', 'Second name', ...']. The list may be empty. Do
218 | not use "TBD" or other placeholders.'''
219 |
220 | # yaml automatically loads list-like strings as lists
221 | return isinstance(helpers, list) and len(helpers) >= 0
222 |
223 |
224 | @look_for_fixme
225 | def check_email(email):
226 | '''"contact" must be a valid email address consisting of characters, a
227 | @, and more characters. It should not be the default contact
228 | email address "admin@software-carpentry.org".'''
229 |
230 | return bool(re.match(EMAIL_PATTERN, email)) and \
231 | (email != DEFAULT_CONTACT_EMAIL)
232 |
233 |
234 | def check_eventbrite(eventbrite):
235 | '''"eventbrite" (the Eventbrite registration key) must be 9 or more digits.'''
236 |
237 | if isinstance(eventbrite, int):
238 | return True
239 | else:
240 | return bool(re.match(EVENTBRITE_PATTERN, eventbrite))
241 |
242 |
243 | @look_for_fixme
244 | def check_etherpad(etherpad):
245 | '''"etherpad" must be a valid URL.'''
246 |
247 | return bool(re.match(URL_PATTERN, etherpad))
248 |
249 |
250 | @look_for_fixme
251 | def check_pass(value):
252 | '''This test always passes (it is used for "checking" things like
253 | addresses, for which no sensible validation is feasible).'''
254 |
255 | return True
256 |
257 |
258 | HANDLERS = {
259 | 'layout': (True, check_layout, 'layout isn\'t "workshop"'),
260 | 'root': (True, check_root, 'root can only be "."'),
261 |
262 | 'country': (True, check_country,
263 | 'country invalid: must use two-letter ISO code from ' +
264 | ', '.join(ISO_COUNTRY)),
265 | 'language' : (False, check_language,
266 | 'language invalid: must use two-letter ISO code from ' +
267 | ', '.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 | 'humantime': (True, check_humantime,
273 | 'humantime doesn\'t include numbers'),
274 | 'startdate': (True, check_date,
275 | 'startdate invalid. Must be of format year-month-day, ' +
276 | 'i.e., 2014-01-31.'),
277 | 'enddate': (False, check_date,
278 | 'enddate invalid. Must be of format year-month-day, i.e.,' +
279 | ' 2014-01-31.'),
280 |
281 | 'latlng': (True, check_latitude_longitude,
282 | 'latlng invalid. Check that it is two floating point ' +
283 | 'numbers, separated by a comma.'),
284 |
285 | 'instructor': (True, check_instructors,
286 | 'instructor list isn\'t a valid list of format ' +
287 | '["First instructor", "Second instructor",..].'),
288 | 'helper': (True, check_helpers,
289 | 'helper list isn\'t a valid list of format ' +
290 | '["First helper", "Second helper",..].'),
291 |
292 | 'contact': (True, check_email,
293 | 'contact email invalid or still set to ' +
294 | '"{0}".'.format(DEFAULT_CONTACT_EMAIL)),
295 |
296 | 'eventbrite': (False, check_eventbrite, 'Eventbrite key appears invalid.'),
297 | 'etherpad': (False, check_etherpad, 'Etherpad URL appears invalid.'),
298 |
299 | 'venue': (False, check_pass, 'venue name not specified'),
300 | 'address': (False, check_pass, 'address not specified')
301 | }
302 |
303 | # REQUIRED is all required categories.
304 | REQUIRED = set([k for k in HANDLERS if HANDLERS[k][0]])
305 |
306 | # OPTIONAL is all optional categories.
307 | OPTIONAL = set([k for k in HANDLERS if not HANDLERS[k][0]])
308 |
309 |
310 | def check_validity(data, function, errors, error_msg):
311 | '''Wrapper-function around the various check-functions.'''
312 | valid = function(data)
313 | if not valid:
314 | add_error(error_msg, errors)
315 | add_suberror('Offending entry is: "{0}"'.format(data), errors)
316 | return valid
317 |
318 |
319 | def check_blank_lines(raw_data, errors, error_msg):
320 | '''Blank lines are not allowed in category headers.'''
321 | lines = [x.strip() for x in raw_data.split('\n')]
322 | if '' in lines:
323 | add_error(error_msg, errors)
324 | add_suberror('{0} blank lines found in header'.format(lines.count('')), errors)
325 | return False
326 | return True
327 |
328 |
329 | def check_categories(left, right, errors, error_msg):
330 | '''Report set difference of categories.'''
331 | result = left - right
332 | if result:
333 | add_error(error_msg, errors)
334 | add_suberror('Offending entries: {0}'.format(result), errors)
335 | return False
336 | return True
337 |
338 |
339 | def get_header(text):
340 | '''Extract YAML header from raw data, returning (None, None) if no
341 | valid header found and (raw, parsed) if header found.'''
342 |
343 | # YAML header must be right at the start of the file.
344 | if not text.startswith('---'):
345 | return None, None
346 |
347 | # YAML header must start and end with '---'
348 | pieces = text.split('---')
349 | if len(pieces) < 3:
350 | return None, None
351 |
352 | # Return raw text and YAML-ized form.
353 | raw = pieces[1].strip()
354 | return raw, yaml.load(raw)
355 |
356 |
357 | def check_file(filename, data):
358 | '''Get header from index.html, call all other functions and check file
359 | for validity. Return list of errors (empty when no errors).'''
360 |
361 | errors = []
362 | raw, header = get_header(data)
363 | if header is None:
364 | msg = ('Cannot find YAML header in given file "{0}".'.format(filename))
365 | add_error(msg, errors)
366 | return errors
367 |
368 | # Do we have any blank lines in the header?
369 | is_valid = check_blank_lines(raw, errors,
370 | 'There are blank lines in the header')
371 |
372 | # Look through all header entries. If the category is in the input
373 | # file and is either required or we have actual data (as opposed to
374 | # a commented-out entry), we check it. If it *isn't* in the header
375 | # but is required, report an error.
376 | for category in HANDLERS:
377 | required, handler_function, error_message = HANDLERS[category]
378 | if category in header:
379 | if required or header[category]:
380 | is_valid &= check_validity(header[category],
381 | handler_function, errors,
382 | error_message)
383 | elif required:
384 | msg = 'index file is missing mandatory key "{0}"'.format(category)
385 | add_error(msg, errors)
386 | is_valid = False
387 |
388 | # Check whether we have missing or too many categories
389 | seen_categories = set(header.keys())
390 |
391 | is_valid &= check_categories(REQUIRED, seen_categories, errors,
392 | 'There are missing categories')
393 |
394 | is_valid &= check_categories(seen_categories, REQUIRED.union(OPTIONAL),
395 | errors, 'There are superfluous categories')
396 |
397 | return errors
398 |
399 |
400 | def main():
401 | '''Run as the main program.'''
402 | filename = None
403 | if len(sys.argv) == 1:
404 | if os.path.exists('./index.html'):
405 | filename = './index.html'
406 | elif os.path.exists('../index.html'):
407 | filename = '../index.html'
408 | elif len(sys.argv) == 2:
409 | filename = sys.argv[1]
410 |
411 | if filename is None:
412 | print(USAGE, file=sys.stderr)
413 | sys.exit(1)
414 |
415 | logger.info('Testing "{0}"'.format(filename))
416 |
417 | with open(filename) as reader:
418 | data = reader.read()
419 | errors = check_file(filename, data)
420 |
421 | if errors:
422 | for m in errors:
423 | logger.error(m)
424 | sys.exit(1)
425 | else:
426 | logger.info('Everything seems to be in order')
427 | sys.exit(0)
428 |
429 |
430 | if __name__ == '__main__':
431 | main()
432 |
--------------------------------------------------------------------------------
/css/swc.css:
--------------------------------------------------------------------------------
1 | /* Card is used only at lessons */
2 | div.container.card {
3 | background-color: white;
4 | }
5 |
6 | /* Headings */
7 | h1, h2, h3, h4, h5, h6 {
8 | font-family: "Open Sans", "Helvetica", "Arial", sans-serif;
9 | font-weight: bold;
10 | }
11 |
12 | h1, h2 {
13 | margin-top: 40px;
14 | margin-bottom: 10px;
15 | }
16 |
17 | h1.title {
18 | margin: 40px 0px;
19 | }
20 |
21 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
22 | color: inherit;
23 | }
24 |
25 | h2 code, h3 code, h4 code, h5 code, h6 code {
26 | font-size: inherit;
27 | }
28 |
29 | /* Objectives, Callout Box, Challenges and prerequisites are now rendered as
30 | Bootstrap panels. There's a pandoc error with rendering headers nested under
31 | blockquote: these headers don't preserve attributes. It means we have to
32 | manually set styles for these headers to look like Bootstrap's "panel-title"
33 | class. */
34 | .panel-heading h1,
35 | .panel-heading h2,
36 | .panel-heading h3,
37 | .panel-heading h4,
38 | .panel-heading h5,
39 | .panel-heading h6 {
40 | margin-top: 0px;
41 | margin-bottom: 0px;
42 | color: inherit;
43 | font-size: 16px;
44 | font-family: inherit;
45 | font-weight: 500;
46 | line-height: 1.1;
47 | }
48 |
49 | /* Make space between the glyphicon and the rest of the header */
50 | .panel-heading h1 span,
51 | .panel-heading h2 span,
52 | .panel-heading h3 span,
53 | .panel-heading h4 span,
54 | .panel-heading h5 span,
55 | .panel-heading h6 span {
56 | padding-right: 10px;
57 | }
58 |
59 | /* Make the CSS compatible with Pandoc <= 1.13.2 and Pandoc > 1.14. */
60 | h1.panel,
61 | h2.panel,
62 | h3.panel,
63 | h4.panel,
64 | h5.panel,
65 | h6.panel {
66 | margin-top: 0px;
67 | margin-bottom: 0px;
68 | border: 0px;
69 | color: inherit;
70 | background-color: inherit;
71 | background-image: inherit;
72 | box-shadow: none;
73 | }
74 |
75 | /* Comments in code. */
76 | .comment {
77 | color: purple;
78 | }
79 |
80 | /* Error messages. */
81 | .err {
82 | color: darkred;
83 | font-style: italic;
84 | }
85 |
86 | /* Things to fix. */
87 | .fixme {
88 | text-decoration: underline;
89 | color: darkred;
90 | background-color: lightgray;
91 | }
92 |
93 | /* Highlighted changes in code. */
94 | .highlight {
95 | background-color: mistyrose;
96 | }
97 |
98 | /* Manual input. */
99 | .in {
100 | color: darkgreen;
101 | }
102 |
103 | /* Program output. */
104 | .out {
105 | color: darkblue;
106 | font-style: italic;
107 | }
108 |
109 | /* Putting shadows around things. */
110 | .shadow {
111 | -moz-box-shadow: 0 0 30px 5px #999;
112 | -webkit-box-shadow: 0 0 30px 5px #999;
113 | box-shadow: 0 0 30px 5px #999;
114 | }
115 |
116 | /* Things to understand (lead-in to sections in book). */
117 | .understand {
118 | background-color: lightyellow;
119 | }
120 |
121 | /* Block quotations. */
122 | blockquote {
123 | margin: 1em;
124 | padding: 1em 1em .5em 1em;
125 | width: 90%;
126 | font-size: inherit;
127 | }
128 |
129 | /* Citation for testimonial quote. */
130 | blockquote.testimonial cite {
131 | font-style: italic;
132 | font-size: inherit;
133 | }
134 |
135 | /* Images
136 | *
137 | * Rules from http://getbootstrap.com/css/#images-responsive.
138 | *
139 | * This is compatible with Pandoc behavior for HTML and HTML5. */
140 | article img {
141 | max-width: 100%;
142 | height: auto;
143 | display: block;
144 | margin-left: auto;
145 | margin-right: auto;
146 | }
147 |
148 | article div.figure,
149 | article figure {
150 | text-align: center;
151 | }
152 |
153 | article p.caption::before,
154 | article figcaption::before {
155 | content: "Figure: ";
156 | }
157 |
158 | /* Main body of pages. */
159 | body {
160 | font-family: "Open Sans", "Helvetica", "Arial", sans-serif;
161 | color: rgb(03, 03, 03);
162 | }
163 |
164 | body.workshop, body.lesson {
165 | background-color: #BEC3C6;
166 | margin: 20px 0;
167 | }
168 |
169 | /* Styling for editorial stylesheet */
170 | body.stylesheet {
171 | background: #ffffff;
172 | width: 60em;
173 | margin: 20px auto;
174 | }
175 |
176 | /* Code sample */
177 | pre.sourceCode,
178 | pre.input {
179 | color: ForestGreen;
180 | }
181 | pre.output {
182 | color: MediumBlue;
183 | }
184 | pre.error {
185 | color: Red;
186 | }
187 |
188 | @media (max-width: 700px) {
189 | div.banner a img {
190 | padding: 20px 0px;
191 | }
192 | }
193 |
194 | /*----------------------------------------*/
195 | /* Override Bootstrap CSS */
196 | /*----------------------------------------*/
197 |
198 | /* navbar */
199 | .navbar {
200 | min-height: 85px;
201 | margin-bottom: 0;
202 | }
203 |
204 | .navbar-nav {
205 | margin: 15px 10px 0px 0px;
206 | }
207 |
208 | #swc-navbar {
209 | border-top: 5px solid #2b3990;
210 | width: 100%;
211 | background: #d6d6d6;
212 | border-bottom: 1px solid #CCC;
213 | }
214 |
215 | .navbar-brand {
216 | position: absolute;
217 | padding-top: 7px;
218 | }
219 |
220 | .navbar-brand img {
221 | width: 250px;
222 | height: 50px;
223 | }
224 |
225 | #swc-navbar-search {
226 | background-color: #ffffff;
227 | color: #666666;
228 | border-color:#2b3990;
229 | width: 150px;
230 | }
231 |
232 | .navbar-default .navbar-nav > li > a {
233 | color: #20267D;
234 | }
235 |
236 | .navbar-nav li {
237 | margin-right: -7px;
238 | margin-left: -7px;
239 | }
240 |
241 | .navbar-nav .navbar {
242 | diplay: inline-block;
243 | float: none;
244 | }
245 |
246 | .navbar .navbar-collapse {
247 | text-align: center;
248 | }
249 |
250 | .navbar-nav .nav-collapse .nav > li > a,
251 | .navbar-nav .nav-collapse .dropdown-menu a {
252 | color: #2b3990;
253 | text-align: center;
254 | }
255 |
256 | .navbar-nav .nav .active > a,
257 | .navbar-nav .nav .active > a:hover, .navbar-nav .nav .active > a:focus {
258 | color: #ffffff;
259 | background-color: #20267D;
260 | }
261 |
262 | .navbar-nav .nav li.dropdown.open > .dropdown-toggle,
263 | .navbar-nav .nav li.dropdown.active > .dropdown-toggle,
264 | .navbar-nav .nav li.dropdown.open.active > .dropdown-toggle {
265 | color: #ffffff;
266 | background-color: #20267D;
267 | }
268 | .navbar-nav .nav li.dropdown.active > .dropdown-toggle .caret {
269 | border-top-color: #999999;
270 | border-bottom-color: #999999;
271 | }
272 | .navbar-nav .nav li.dropdown.open > .dropdown-toggle .caret {
273 | border-top-color: #ffffff;
274 | border-bottom-color: #ffffff;
275 | }
276 |
277 | blockquote p {
278 | font-size: inherit;
279 | font-weight: inherit;
280 | line-height: inherit;
281 | }
282 |
283 | blockquote h2{
284 | margin-top: 0px;
285 | }
286 |
287 | /* readability: darken the alert colour for contrast with background */
288 |
289 | .alert {
290 | color: rgb(0, 0, 0);
291 | }
292 |
293 | code {
294 | color: #333333;
295 | }
296 |
297 | /* Top banner of every page. */
298 | div.banner {
299 | background-color: #FFFFFF;
300 | width: 100%;
301 | height: 90px;
302 | margin: 0px;
303 | padding: 0;
304 | border-bottom: 1px solid #A6A6A6;
305 | }
306 |
307 | /* Padding around image in top banner. */
308 | div.banner a img {
309 | padding: 20px 25px;
310 | }
311 |
312 | /* Explanatory call-out boxes. */
313 | div.box {
314 | width: 54em;
315 | background-color: mistyrose;
316 | display: block;
317 | margin-left: auto;
318 | margin-right: auto;
319 | padding-top: 1px;
320 | padding-bottom: 1px;
321 | padding-left: 10px;
322 | padding-right: 10px;
323 | outline-color: gray;
324 | outline-width: 1px;
325 | outline-style: solid;
326 | }
327 |
328 | /* Level 2 headings inside pages. */
329 | div.content div h3 {
330 | border-bottom: 1px solid #CCCCCC;
331 | display: block;
332 | font-family: Verdana,"BitStream vera Sans";
333 | margin-top: 10px;
334 | padding: 0 5px 3px;
335 | }
336 |
337 | /* PDF and slide files referenced from lectures. */
338 | div.files {
339 | padding: 10px;
340 | }
341 |
342 | .swc-blue-bg {
343 | /*background-color: #20267D;*/
344 | /* svg colour is slightly different? */
345 | background: #2b3990;
346 | }
347 |
348 | /* Main menu at the top of every page. */
349 | div.mainmenu {
350 | clear: both;
351 | background-color: #F4F4F4;
352 | margin: 0px;
353 | padding: 3px 0px 3px 25px;
354 | border-bottom: 1px solid #A6A6A6;
355 | height: 30px
356 | }
357 |
358 | /* Narration for audio-only lectures. */
359 | div.narration {
360 | text-align: center;
361 | font-size: 2em;
362 | }
363 |
364 | /* Table of contents. */
365 | div.toc {
366 | /* No special styling yet. */
367 | }
368 |
369 | .transcript {
370 | display: table;
371 | }
372 |
373 | .transcript .media img {
374 | border: 1px solid grey;
375 | }
376 |
377 | /* YouTube video embed. */
378 | div.youtube {
379 | text-align: center;
380 | padding: 10px;
381 | }
382 |
383 | /* Glossary description lists. */
384 | dl.gloss {
385 | /* Empty for now. */
386 | }
387 |
388 | /* Displaying YouTube videos. */
389 | iframe.youtube_player {
390 | border: 0;
391 | text-align: center;
392 | width: 640px;
393 | height: 500px;
394 | }
395 |
396 | /* Sections in book chapters. */
397 | section {
398 | clear: both;
399 | }
400 |
401 | /* Person's name in team.html. */
402 | .person {
403 | font-weight: bold;
404 | font-style: italic;
405 | }
406 |
407 | /* Short review of book in bibliography. */
408 | span.review {
409 | font-style: italic;
410 | }
411 |
412 | /* Bibliography book covers. */
413 | img.book-cover {
414 | width: 80px;
415 | }
416 |
417 | /* Blog calendar table in blog/index.html. */
418 | table.blogcalendar th {
419 | text-align : right;
420 | font-weight : bold;
421 | }
422 |
423 | /* See above. */
424 | table.blogcalendar th.left {
425 | text-align : left;
426 | }
427 |
428 | /* See above. */
429 | table.blogcalendar tr td {
430 | text-align : right;
431 | }
432 |
433 | /* Blog index tables in blog/index.html. */
434 | table.blogindex td.date {
435 | text-align: left ;
436 | width:10em;
437 | }
438 |
439 | /* Tables used for displaying choices in challenges. */
440 | table.choices tr td {
441 | vertical-align : top;
442 | }
443 |
444 | /* Database tables do _not_ have double borders */
445 | table.outlined {
446 | border-collapse: collapse;
447 | }
448 |
449 | /* Link items (to workshop pages) in the workshops tables */
450 | table.workshops td.link {
451 | width: 50%;
452 | text-align: left;
453 | }
454 |
455 | /* Spacer items (i.e. ellipsis) on the workshops tables */
456 | table.workshops td.spacer {
457 | max-width: 100%;
458 | text-align: center;
459 | }
460 |
461 | /* Date columns on the workshops tables */
462 | table.workshops td.date {
463 | width: 50%;
464 | text-align: right;
465 | }
466 |
467 | /* Badge modal dialog */
468 | #assertion-modal {
469 | width:700px;
470 | margin-left:-350px;
471 | }
472 | #assertion-modal iframe {
473 | background-color: transparent;
474 | border: 0px none transparent;
475 | padding: 0px;
476 | width: 100%;
477 | height: 20em;
478 | }
479 |
480 | #assertion-model img.badge {
481 | position: absolute;
482 | right: 15px;
483 | bottom: 35px;
484 | opacity: 0.5;
485 | }
486 |
487 | #modal-badge-img {
488 | position:absolute;
489 | right: 25px;
490 | bottom: 25px;
491 | opacity: 0.5;
492 | }
493 | #assertion-modal.in {
494 | color: black;
495 | }
496 |
497 | /* list with checkbox as bullet */
498 | ul.checklist {
499 | list-style-image: url('../img/checkbox.png');
500 | }
501 |
502 | /* FAQ */
503 | dl.faq dt {
504 | font-style: italic;
505 | font-weight: bold;
506 | }
507 |
508 | section.content {
509 | width:100%;
510 | background: white;
511 | }
512 |
513 | dd {
514 | margin-left: 10px;
515 | }
516 |
517 | .header.home {
518 | background: url(../img/header.png) no-repeat center center;
519 | background-attachment: fixed;
520 | -webkit-background-size: cover;
521 | -moz-background-size: cover;
522 | -o-background-size: cover;
523 | background-size: cover;
524 | }
525 |
526 | .header {
527 | background:#2b3990;
528 | }
529 |
530 | .header h1 {
531 | line-height: 1.1;
532 | margin: 60px 0px 80px;
533 | font-size: 40pt;
534 | }
535 |
536 | table {
537 | margin-bottom: 15px;
538 | }
539 |
540 | table th, table td {
541 | padding: 5px 10px;
542 | }
543 |
544 | table > thead > .header {
545 | background: transparent;
546 | }
547 |
548 | table > thead > tr > td, table > thead > tr > th,
549 | table > tbody > tr > td, table > tbody > tr > th,
550 | table > tfoot > tr > td, table > tfoot > tr > th {
551 | border: 1px solid #DDD;
552 | }
553 |
554 | table > thead > tr > th,
555 | table > thead > tr > td {
556 | border-bottom-width: 2px;
557 | }
558 |
559 | table tbody > tr:nth-of-type(2n+1) {
560 | background-color: #F9F9F9;
561 | }
562 |
563 | #header-text {
564 | font-size:20pt;
565 | margin:0;
566 | }
567 |
568 | #home-options {
569 | background:#F6F6F6;
570 | border-top:1px solid #DDDDDD;
571 | border-bottom:1px solid #DDDDDD;
572 | padding:20px 0;
573 | margin-bottom:20px;
574 | }
575 |
576 | #title {
577 | background:#F6F6F6;
578 | border-top:1px solid #DDDDDD;
579 | border-bottom:1px solid #DDDDDD;
580 | padding:0 0 20px;
581 | margin-bottom:20px;
582 | }
583 |
584 | h5 a:link, h5 a:visited,
585 | h4 a:link, h4 a:visited,
586 | h3 a:link, h3 a:visited {
587 | color:#2b3990;
588 | }
589 |
590 | h5 a:hover,
591 | h4 a:hover,
592 | h3 a:hover {
593 | color:#C26D17;
594 | text-decoration: none;
595 | }
596 |
597 | a {
598 | color:#3E51CF;
599 | -webkit-transition: all 0.2s ease;
600 | -moz-transition: all 0.2s ease;
601 | -o-transition: all 0.2s ease;
602 | transition: all 0.2s ease;
603 | }
604 |
605 | a:hover {
606 | color:#965412;
607 | }
608 |
609 | footer {
610 | background: #2b3990;
611 | padding: 20px 0;
612 | font-size: 10pt;
613 | margin-top: 10px;
614 | }
615 |
616 | footer a, footer a:hover{
617 | color:#FFF;
618 | padding-left: 10px;
619 | }
620 |
621 | footer .container .links {
622 | background:url('../img/software-carpentry-banner-white.png') no-repeat;
623 | background-size: 200px;
624 | background-position: 0;
625 | min-height: 40px;
626 | padding: 20px 0px 0px 200px;
627 | text-align: right;
628 | }
629 |
630 |
631 | /* Footer of every page. */
632 | /* TODO -- might clash with site footer */
633 | div.footer {
634 | clear: both;
635 | background: url("/img/main_shadow.png") repeat-x scroll center top #FFFFFF;
636 | padding: 4px 10px 7px 10px;
637 | border-top: 1px solid #A6A6A6;
638 | text-align: right;
639 | }
640 |
641 |
642 | /* doesn't seem to be used on site, workshop or lesson */
643 | /* Chapter titles. */
644 | div.chapter h2 {
645 | text-align: center;
646 | font-style: italic;
647 | }
648 |
649 |
650 | /* For the Request a Workshop form */
651 | #ss-form .ss-q-title {
652 | display: block;
653 | font-weight: bold;
654 | padding-bottom: 0.5em;
655 | }
656 | #ss-form .ss-required-asterisk {
657 | color: #c43b1d;
658 | }
659 | #ss-form label {
660 | display:inline;
661 | cursor: default;
662 | }
663 | #ss-form .ss-secondary-text {
664 | color: #666;
665 | }
666 | #ss-form .ss-form-entry {
667 | margin-bottom: 1.5em;
668 | }
669 | #ss-form ul {
670 | margin:0;
671 | padding:0;
672 | list-style: none;
673 | }
674 | #ss-form ol {
675 | margin:0;
676 | }
677 | #ss-form .ss-choice-item {
678 | line-height: 1.3em;
679 | padding-bottom: .5em;
680 | }
681 | #ss-form .ss-q-long {
682 | resize: vertical;
683 | width: 70%;
684 | }
685 | #ss-form .error-message, .required-message {
686 | display: none;
687 | }
688 | #ss-form .ss-form-entry input {
689 | vertical-align: middle;
690 | margin: 0;
691 | padding:0 4px;
692 | }
693 | #ss-form .ss-choice-item-control {
694 | padding-right: 4px;
695 | }
696 |
697 |
698 | /* GitHub Ribbon */
699 | #github-ribbon a {
700 | background: #000;
701 | color: #fff;
702 | text-decoration: none;
703 | font-family: arial, sans-serif;
704 | text-align: center;
705 | font-weight: bold;
706 | padding: 5px 40%;
707 | font-size: 1.2rem;
708 | line-height: 2rem;
709 | position: relative;
710 | transition: 0.5s;
711 | width: 100%;
712 | margin: 0 auto;
713 | white-space: nowrap;
714 | z-index: 99999;
715 | }
716 | #github-ribbon a:hover {
717 | background: #600;
718 | color: #fff;
719 | }
720 | #github-ribbon a::before, #github-ribbon a::after {
721 | content: "";
722 | width: 100%;
723 | display: block;
724 | position: absolute;
725 | top: 1px;
726 | left: 0;
727 | height: 1px;
728 | background: #fff;
729 | }
730 | #github-ribbon a::after {
731 | bottom: 1px;
732 | top: auto;
733 | }
734 |
735 | /* Collapse navbar */
736 | @media (max-width: 993px) {
737 | .navbar-header {
738 | float: none;
739 | min-height: 80px;
740 | }
741 | .navbar-left,.navbar-right {
742 | float: none !important;
743 | }
744 | .navbar-toggle {
745 | display: block;
746 | background-color: #2b3990;
747 | margin-top: 22px;
748 | margin-right: 100px;
749 | }
750 | .navbar-collapse {
751 | border-top: 1px solid transparent;
752 | box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
753 | }
754 | .navbar-fixed-top {
755 | top: 0;
756 | border-width: 0 0 1px;
757 | }
758 | .navbar-collapse.collapse {
759 | display: none!important;
760 | }
761 | .navbar-nav {
762 | float: none!important;
763 | }
764 | .navbar-nav>li {
765 | float: none;
766 | }
767 | .navbar-nav>li>a {
768 | padding-top: 10px;
769 | padding-bottom: 10px;
770 | }
771 | .collapse.in{
772 | display:block !important;
773 | }
774 | }
775 |
776 | @media (max-width: 600px) {
777 | .navbar-toggle {
778 | margin-right: 20px;
779 | }
780 | .navbar-brand img {
781 | width: 180px;
782 | height: 36px;
783 | margin-top: 10px
784 | }
785 | footer .container .links {
786 | background:url('../img/software-carpentry-banner-white.png') no-repeat;
787 | background-size: 180px;
788 | background-position: 0;
789 | }
790 | }
791 |
792 | /* GitHub ribbon breaking point */
793 | @media screen and (min-width: 600px) {
794 | #github-ribbon {
795 | position: absolute;
796 | display: block;
797 | top: 0;
798 | right: 0;
799 | width: 150px;
800 | overflow: hidden;
801 | height: 150px;
802 | }
803 | #github-ribbon a {
804 | width: 200px;
805 | position: absolute;
806 | padding: 5px 40px;
807 | top: 40px;
808 | right: -40px;
809 | transform: rotate(45deg);
810 | -webkit-transform: rotate(45deg);
811 | box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.8);
812 | }
813 | }
814 | @media (max-width: 599px) {
815 | .header h1 {
816 | font-size: 20pt;
817 | }
818 | #header-text {
819 | font-size: 16pt;
820 | }
821 | #github-ribbon {
822 | width: 100%;
823 | }
824 | #github-ribbon a {
825 | display: block;
826 | padding: 0px 0px;
827 | margin: 0px 0px;
828 | }
829 | }
830 |
--------------------------------------------------------------------------------
/css/bootstrap/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.4 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | .btn-default,
8 | .btn-primary,
9 | .btn-success,
10 | .btn-info,
11 | .btn-warning,
12 | .btn-danger {
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
16 | }
17 | .btn-default:active,
18 | .btn-primary:active,
19 | .btn-success:active,
20 | .btn-info:active,
21 | .btn-warning:active,
22 | .btn-danger:active,
23 | .btn-default.active,
24 | .btn-primary.active,
25 | .btn-success.active,
26 | .btn-info.active,
27 | .btn-warning.active,
28 | .btn-danger.active {
29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
31 | }
32 | .btn-default .badge,
33 | .btn-primary .badge,
34 | .btn-success .badge,
35 | .btn-info .badge,
36 | .btn-warning .badge,
37 | .btn-danger .badge {
38 | text-shadow: none;
39 | }
40 | .btn:active,
41 | .btn.active {
42 | background-image: none;
43 | }
44 | .btn-default {
45 | text-shadow: 0 1px 0 #fff;
46 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
47 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
48 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
49 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
52 | background-repeat: repeat-x;
53 | border-color: #dbdbdb;
54 | border-color: #ccc;
55 | }
56 | .btn-default:hover,
57 | .btn-default:focus {
58 | background-color: #e0e0e0;
59 | background-position: 0 -15px;
60 | }
61 | .btn-default:active,
62 | .btn-default.active {
63 | background-color: #e0e0e0;
64 | border-color: #dbdbdb;
65 | }
66 | .btn-default.disabled,
67 | .btn-default:disabled,
68 | .btn-default[disabled] {
69 | background-color: #e0e0e0;
70 | background-image: none;
71 | }
72 | .btn-primary {
73 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
74 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
75 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
76 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
79 | background-repeat: repeat-x;
80 | border-color: #245580;
81 | }
82 | .btn-primary:hover,
83 | .btn-primary:focus {
84 | background-color: #265a88;
85 | background-position: 0 -15px;
86 | }
87 | .btn-primary:active,
88 | .btn-primary.active {
89 | background-color: #265a88;
90 | border-color: #245580;
91 | }
92 | .btn-primary.disabled,
93 | .btn-primary:disabled,
94 | .btn-primary[disabled] {
95 | background-color: #265a88;
96 | background-image: none;
97 | }
98 | .btn-success {
99 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
100 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
101 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
102 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
103 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
104 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
105 | background-repeat: repeat-x;
106 | border-color: #3e8f3e;
107 | }
108 | .btn-success:hover,
109 | .btn-success:focus {
110 | background-color: #419641;
111 | background-position: 0 -15px;
112 | }
113 | .btn-success:active,
114 | .btn-success.active {
115 | background-color: #419641;
116 | border-color: #3e8f3e;
117 | }
118 | .btn-success.disabled,
119 | .btn-success:disabled,
120 | .btn-success[disabled] {
121 | background-color: #419641;
122 | background-image: none;
123 | }
124 | .btn-info {
125 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
126 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
127 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
128 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
129 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
130 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
131 | background-repeat: repeat-x;
132 | border-color: #28a4c9;
133 | }
134 | .btn-info:hover,
135 | .btn-info:focus {
136 | background-color: #2aabd2;
137 | background-position: 0 -15px;
138 | }
139 | .btn-info:active,
140 | .btn-info.active {
141 | background-color: #2aabd2;
142 | border-color: #28a4c9;
143 | }
144 | .btn-info.disabled,
145 | .btn-info:disabled,
146 | .btn-info[disabled] {
147 | background-color: #2aabd2;
148 | background-image: none;
149 | }
150 | .btn-warning {
151 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
152 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
153 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
154 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
155 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
156 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
157 | background-repeat: repeat-x;
158 | border-color: #e38d13;
159 | }
160 | .btn-warning:hover,
161 | .btn-warning:focus {
162 | background-color: #eb9316;
163 | background-position: 0 -15px;
164 | }
165 | .btn-warning:active,
166 | .btn-warning.active {
167 | background-color: #eb9316;
168 | border-color: #e38d13;
169 | }
170 | .btn-warning.disabled,
171 | .btn-warning:disabled,
172 | .btn-warning[disabled] {
173 | background-color: #eb9316;
174 | background-image: none;
175 | }
176 | .btn-danger {
177 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
178 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
179 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
180 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
181 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
182 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
183 | background-repeat: repeat-x;
184 | border-color: #b92c28;
185 | }
186 | .btn-danger:hover,
187 | .btn-danger:focus {
188 | background-color: #c12e2a;
189 | background-position: 0 -15px;
190 | }
191 | .btn-danger:active,
192 | .btn-danger.active {
193 | background-color: #c12e2a;
194 | border-color: #b92c28;
195 | }
196 | .btn-danger.disabled,
197 | .btn-danger:disabled,
198 | .btn-danger[disabled] {
199 | background-color: #c12e2a;
200 | background-image: none;
201 | }
202 | .thumbnail,
203 | .img-thumbnail {
204 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
205 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
206 | }
207 | .dropdown-menu > li > a:hover,
208 | .dropdown-menu > li > a:focus {
209 | background-color: #e8e8e8;
210 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
211 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
212 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
213 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
214 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
215 | background-repeat: repeat-x;
216 | }
217 | .dropdown-menu > .active > a,
218 | .dropdown-menu > .active > a:hover,
219 | .dropdown-menu > .active > a:focus {
220 | background-color: #2e6da4;
221 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
222 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
223 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
224 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
225 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
226 | background-repeat: repeat-x;
227 | }
228 | .navbar-default {
229 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
230 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
231 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
232 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
233 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
234 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
235 | background-repeat: repeat-x;
236 | border-radius: 4px;
237 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
238 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
239 | }
240 | .navbar-default .navbar-nav > .open > a,
241 | .navbar-default .navbar-nav > .active > a {
242 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
243 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
244 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
245 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
246 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
247 | background-repeat: repeat-x;
248 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
249 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
250 | }
251 | .navbar-brand,
252 | .navbar-nav > li > a {
253 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
254 | }
255 | .navbar-inverse {
256 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
257 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
258 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
259 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
260 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
261 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
262 | background-repeat: repeat-x;
263 | }
264 | .navbar-inverse .navbar-nav > .open > a,
265 | .navbar-inverse .navbar-nav > .active > a {
266 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
267 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
268 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
269 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
270 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
271 | background-repeat: repeat-x;
272 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
273 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
274 | }
275 | .navbar-inverse .navbar-brand,
276 | .navbar-inverse .navbar-nav > li > a {
277 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
278 | }
279 | .navbar-static-top,
280 | .navbar-fixed-top,
281 | .navbar-fixed-bottom {
282 | border-radius: 0;
283 | }
284 | @media (max-width: 767px) {
285 | .navbar .navbar-nav .open .dropdown-menu > .active > a,
286 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
287 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
288 | color: #fff;
289 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
290 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
291 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
292 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
294 | background-repeat: repeat-x;
295 | }
296 | }
297 | .alert {
298 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
299 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
300 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
301 | }
302 | .alert-success {
303 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
304 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
305 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
306 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
307 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
308 | background-repeat: repeat-x;
309 | border-color: #b2dba1;
310 | }
311 | .alert-info {
312 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
313 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
314 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
315 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
316 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
317 | background-repeat: repeat-x;
318 | border-color: #9acfea;
319 | }
320 | .alert-warning {
321 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
322 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
323 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
324 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
325 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
326 | background-repeat: repeat-x;
327 | border-color: #f5e79e;
328 | }
329 | .alert-danger {
330 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
331 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
332 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
333 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
334 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
335 | background-repeat: repeat-x;
336 | border-color: #dca7a7;
337 | }
338 | .progress {
339 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
340 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
342 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
344 | background-repeat: repeat-x;
345 | }
346 | .progress-bar {
347 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
348 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
349 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
350 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
352 | background-repeat: repeat-x;
353 | }
354 | .progress-bar-success {
355 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
356 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
357 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
358 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
360 | background-repeat: repeat-x;
361 | }
362 | .progress-bar-info {
363 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
364 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
365 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
366 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
367 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
368 | background-repeat: repeat-x;
369 | }
370 | .progress-bar-warning {
371 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
372 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
373 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
374 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
375 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
376 | background-repeat: repeat-x;
377 | }
378 | .progress-bar-danger {
379 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
380 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
381 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
382 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
383 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
384 | background-repeat: repeat-x;
385 | }
386 | .progress-bar-striped {
387 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
388 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
389 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
390 | }
391 | .list-group {
392 | border-radius: 4px;
393 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
394 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
395 | }
396 | .list-group-item.active,
397 | .list-group-item.active:hover,
398 | .list-group-item.active:focus {
399 | text-shadow: 0 -1px 0 #286090;
400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
405 | background-repeat: repeat-x;
406 | border-color: #2b669a;
407 | }
408 | .list-group-item.active .badge,
409 | .list-group-item.active:hover .badge,
410 | .list-group-item.active:focus .badge {
411 | text-shadow: none;
412 | }
413 | .panel {
414 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
415 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
416 | }
417 | .panel-default > .panel-heading {
418 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
419 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
420 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
421 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
422 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
423 | background-repeat: repeat-x;
424 | }
425 | .panel-primary > .panel-heading {
426 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
427 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
428 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
429 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
430 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
431 | background-repeat: repeat-x;
432 | }
433 | .panel-success > .panel-heading {
434 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
435 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
436 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
437 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
438 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
439 | background-repeat: repeat-x;
440 | }
441 | .panel-info > .panel-heading {
442 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
443 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
444 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
445 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
447 | background-repeat: repeat-x;
448 | }
449 | .panel-warning > .panel-heading {
450 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
451 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
453 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
455 | background-repeat: repeat-x;
456 | }
457 | .panel-danger > .panel-heading {
458 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
459 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
461 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
463 | background-repeat: repeat-x;
464 | }
465 | .well {
466 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
467 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
469 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
471 | background-repeat: repeat-x;
472 | border-color: #dcdcdc;
473 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
474 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
475 | }
476 | /*# sourceMappingURL=bootstrap-theme.css.map */
477 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | layout: workshop
3 | root: .
4 | venue: Space Telescope Science Institute
5 | address: 3700 San Martin Drive, Baltimore, Maryland 21218
6 | country: us
7 | language: en
8 | room: Boardroom
9 | latlng: 39.332728, -76.623277
10 | humandate: Sep 24-25, 2015
11 | humantime: 9:00 am - 5:00 pm
12 | startdate: 2015-09-24
13 | enddate: 2015-09-25
14 | instructor: [Erik Bray, Justin Ely]
15 | helper: [Joseph Long]
16 | contact: embray@stsci.edu
17 | etherpad: https://swcarpentry.etherpad.mozilla.org/2015-09-24-stsci
18 | eventbrite: # optional (insert the alphanumeric key for Eventbrite registration, e.g., "1234567890AB")
19 | ---
20 |
28 |
29 |
37 | {% if page.eventbrite %}
38 |
45 | {% endif %}
46 |
47 | General Information
48 |
49 |
55 |
56 | Software Carpentry's mission
57 | is to help scientists and engineers get more research done in less
58 | time and with less pain by teaching them basic lab skills for
59 | scientific computing. This hands-on workshop will cover basic
60 | concepts and tools, including program design, version control, data
61 | management, and task automation. Participants will be encouraged to
62 | help one another and to apply what they have learned to their own
63 | research problems.
64 |
65 |
66 |
67 | For more information on what we teach and why,
68 | please see our paper
69 | "Best Practices for Scientific Computing".
70 |
71 |
72 |
73 |
79 |
80 | Who:
81 | Faculty, staff, and post-docs in the INS division.
82 | You don't need to have any previous knowledge of the tools that will
83 | be presented at the workshop, though you will be expected to have some experience working with the command line shell.
84 |
85 |
86 |
94 | {% if page.latlng %}
95 |
96 | Where:
97 | {{page.address}}.
98 | Get directions with
99 | OpenStreetMap
100 | or
101 | Google Maps.
102 |
103 | {% endif %}
104 |
105 |
110 |
111 | Requirements: Participants must bring a laptop with
112 | a few specific software packages installed (listed
113 | below). They are also required to abide by
114 | Software Carpentry's
115 | Code of Conduct.
116 |
117 |
118 |
125 |
126 | Contact:
127 | Please mail
128 | {% if page.contact %}
129 | {{page.contact}}
130 | {% else %}
131 | {{site.contact}}
132 | {% endif %}
133 | for more information.
134 |
135 |
136 |
137 |
138 |
145 | Schedule
146 | A loose schedule is outlined below, but we're really here to teach the areas
147 | +of most interest to the students. As such, the topics for each section may
148 | +change based on student feedback. Coffee breaks will be interpersed throughout. Lunch will be at noon.
149 |
150 |
151 |
Day 1
152 |
153 | | Setup and introductions |
154 | | Discussion of Unix shell with intermediate tips |
155 | | Introduction to scientific programming and data analysis with Python |
156 | | Split: Python for beginners |
157 | | Split: Building programs with Python |
158 | | Testing with Python |
159 |
160 |
161 |
162 |
Day 2
163 |
164 | | Version control with Git |
165 | | Managing data with SQL |
166 | | Programming for databases in Python |
167 | | Wrap-up |
168 |
169 |
170 |
171 |
172 |
186 | {% if page.etherpad %}
187 |
188 | Etherpad: {{page.etherpad}}.
189 |
190 | We will use this Etherpad for chatting, taking notes, and sharing URLs and bits of code.
191 |
192 | {% endif %}
193 |
194 |
195 |
196 |
213 | Syllabus
214 |
215 |
216 |
217 |
218 |
The Unix Shell
219 |
220 | - Files and directories
221 | - History and tab completion
222 | - Pipes and redirection
223 | - Looping over files
224 | - Creating and running shell scripts
225 | - Finding things
226 | - Reference...
227 |
228 |
229 |
230 |
231 |
Programming in Python
232 |
233 | - Using libraries
234 | - Working with arrays
235 | - Reading and plotting data
236 | - Creating and using functions
237 | - Loops and conditionals
238 | - Defensive programming
239 | - Using Python from the command line
240 | - Reference...
241 |
242 |
243 |
256 |
269 |
270 |
271 |
272 |
273 |
Version Control with Git
274 |
275 | - Creating a repository
276 | - Recording changes to files:
add, commit, ...
277 | - Viewing changes:
status, diff, ...
278 | - Ignoring files
279 | - Working on the web:
clone, pull, push, ...
280 | - Resolving conflicts
281 | - Open licenses
282 | - Where to host work, and why
283 | - Reference...
284 |
285 |
286 |
287 |
Managing Data with SQL
288 |
289 | - Reading and sorting data
290 | - Filtering with
where
291 | - Calculating new values on the fly
292 | - Handling missing values
293 | - Combining values using aggregation
294 | - Combining information from multiple tables using
join
295 | - Creating, modifying, and deleting data
296 | - Programming with databases
297 | - Reference...
298 |
299 |
300 |
301 |
302 |
303 |
304 |
315 |
316 | Setup
317 |
318 |
319 | To participate in this Software Carpentry workshop, you will need
320 | access to the software described below.
321 |
322 |
323 | Most of the software outlined below should be pre-installed on your system (particularly if
324 | you're using an STScI-provided laptop). However, you should go over each section and veryify that
325 | you do indeed have it installed.
326 |
327 |
328 |
329 |
The Bash Shell
330 |
331 |
332 | Bash is a commonly-used shell that gives you the power to do simple
333 | tasks more quickly.
334 |
335 |
336 |
337 |
338 |
Windows
339 |
340 | Download the Git for Windows
341 | installer. Run the
342 | installer. Important: on the 6th page of the installation wizard (the
343 | page titled `Configuring the terminal emulator...`) select `Use Windows'
344 | default console window`. If you forgot to do this programs that you need
345 | for the workshop will not work properly. If this happens rerun the
346 | installer and select the appropriate option. This will provide you
347 | with both Git and Bash in the Git Bash program.
348 |
349 |
350 |
351 |
Mac OS X
352 |
353 | The default shell in all versions of Mac OS X is bash, so no
354 | need to install anything. You access bash from the Terminal
355 | (found in
356 | /Applications/Utilities). You may want to keep
357 | Terminal in your dock for this workshop.
358 |
359 |
360 |
361 |
Linux
362 |
363 | The default shell is usually Bash, but if your
364 | machine is set up differently you can run it by opening a
365 | terminal and typing bash. There is no need to
366 | install anything.
367 |
368 |
369 |
370 |
371 |
372 |
374 |
Git
375 |
376 |
377 | Git is a version control system that lets you track who made changes
378 | to what when and has options for easily updating a shared or public
379 | version of your code
380 | on github.com. You will need a
381 | supported
382 | web browser (current versions of Chrome, Firefox or Safari,
383 | or Internet Explorer version 9 or above).
384 |
385 |
386 |
387 |
388 |
Windows
389 |
390 | Git should be installed on your computer as part of your Bash
391 | install (described above).
392 |
393 |
394 |
395 |
Mac OS X
396 |
397 | For OS X 10.9 and higher, install Git for Mac
398 | by downloading and running the most recent "mavericks" installer from
399 | this list.
400 | After installing Git, there will not be anything in your /Applications folder,
401 | as Git is a command line program.
402 | For older versions of OS X (10.5-10.8) use the
403 | most recent available installer labelled "snow-leopard"
404 | available here.
405 |
406 |
407 |
408 |
Linux
409 |
410 | If Git is not already available on your machine you can try to
411 | install it via your distro's package manager. For Debian/Ubuntu run
412 | sudo apt-get install git and for Fedora run
413 | sudo yum install git.
414 |
415 |
416 |
417 |
418 |
479 |
480 |
481 |
485 |
Python
486 |
487 |
488 | Python is a popular language for
489 | scientific computing, and great for general-purpose programming as
490 | well. For this course we'll be using the standard STScI science package
491 | Ureka. If you're using an
492 | STScI laptop, this should already be installed.
493 |
494 |
495 |
496 | We will teach Python using the IPython notebook, a programming environment
497 | that runs in a web browser. For this to work you will need a reasonably
498 | up-to-date browser. The current versions of the Chrome, Safari and
499 | Firefox browsers are all supported
501 | (some older browsers, including Internet Explorer version 9
502 | and below, are not).
503 |
504 |
561 |
562 |
563 |
608 |
609 |
610 |
SQLite
611 |
612 |
613 | SQL is a specialized programming language used with databases. We
614 | use a simple database manager called
615 | SQLite in our lessons.
616 |
617 |
618 |
619 |
620 |
Windows
621 |
622 | The Software Carpentry Windows Installer
623 | installs SQLite for Windows.
624 | If you used the installer to configure nano, you don't need to run it again.
625 |
626 |
627 |
628 |
Mac OS X
629 |
630 | SQLite comes pre-installed on Mac OS X.
631 |
632 |
633 |
634 |
Linux
635 |
636 | SQLite comes pre-installed on Linux.
637 |
638 |
639 |
640 |
641 |
642 |
643 |
670 |
--------------------------------------------------------------------------------
/setup/swc-installation-test-2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """Test script to check for required functionality.
4 |
5 | Execute this code at the command line by typing:
6 |
7 | python swc-installation-test-2.py
8 |
9 | Run the script and follow the instructions it prints at the end.
10 |
11 | This script requires at least Python 2.6. You can check the version
12 | of Python that you have installed with 'swc-installation-test-1.py'.
13 |
14 | By default, this script will test for all the dependencies your
15 | instructor thinks you need. If you want to test for a different set
16 | of packages, you can list them on the command line. For example:
17 |
18 | python swc-installation-test-2.py git virtual-editor
19 |
20 | This is useful if the original test told you to install a more recent
21 | version of a particular dependency, and you just want to re-test that
22 | dependency.
23 | """
24 |
25 | from __future__ import print_function # for Python 2.6 compatibility
26 |
27 | import distutils.ccompiler as _distutils_ccompiler
28 | import fnmatch as _fnmatch
29 | try: # Python 2.7 and 3.x
30 | import importlib as _importlib
31 | except ImportError: # Python 2.6 and earlier
32 | class _Importlib (object):
33 | """Minimal workarounds for functions we need
34 | """
35 | @staticmethod
36 | def import_module(name):
37 | module = __import__(name)
38 | for n in name.split('.')[1:]:
39 | module = getattr(module, n)
40 | return module
41 | _importlib = _Importlib()
42 | import logging as _logging
43 | import os as _os
44 | import platform as _platform
45 | import re as _re
46 | import shlex as _shlex
47 | import subprocess as _subprocess
48 | import sys as _sys
49 | try: # Python 3.x
50 | import urllib.parse as _urllib_parse
51 | except ImportError: # Python 2.x
52 | import urllib as _urllib_parse # for quote()
53 |
54 |
55 | if not hasattr(_shlex, 'quote'): # Python versions older than 3.3
56 | # Use the undocumented pipes.quote()
57 | import pipes as _pipes
58 | _shlex.quote = _pipes.quote
59 |
60 |
61 | __version__ = '0.1'
62 |
63 | # Comment out any entries you don't need
64 | CHECKS = [
65 | # Shell
66 | 'virtual-shell',
67 | # Editors
68 | 'virtual-editor',
69 | # Browsers
70 | 'virtual-browser',
71 | # Version control
72 | 'git',
73 | 'hg', # Command line tool
74 | #'mercurial', # Python package
75 | 'EasyMercurial',
76 | # Build tools and packaging
77 | 'make',
78 | 'virtual-pypi-installer',
79 | 'setuptools',
80 | #'xcode',
81 | # Testing
82 | 'nosetests', # Command line tool
83 | 'nose', # Python package
84 | 'py.test', # Command line tool
85 | 'pytest', # Python package
86 | # SQL
87 | 'sqlite3', # Command line tool
88 | 'sqlite3-python', # Python package
89 | # Python
90 | 'python',
91 | 'ipython', # Command line tool
92 | 'IPython', # Python package
93 | 'argparse', # Useful for utility scripts
94 | 'numpy',
95 | 'scipy',
96 | 'matplotlib',
97 | 'pandas',
98 | 'sympy',
99 | 'Cython',
100 | 'networkx',
101 | 'mayavi.mlab',
102 | ]
103 |
104 | CHECKER = {}
105 |
106 | _ROOT_PATH = _os.sep
107 | if _platform.system() == 'win32':
108 | _ROOT_PATH = 'c:\\'
109 |
110 |
111 | class InvalidCheck (KeyError):
112 | def __init__(self, check):
113 | super(InvalidCheck, self).__init__(check)
114 | self.check = check
115 |
116 | def __str__(self):
117 | return self.check
118 |
119 |
120 | class DependencyError (Exception):
121 | _default_url = 'http://software-carpentry.org/setup/'
122 | _setup_urls = { # (system, version, package) glob pairs
123 | ('*', '*', 'Cython'): 'http://docs.cython.org/src/quickstart/install.html',
124 | ('Linux', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-linux',
125 | ('Darwin', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-mac',
126 | ('Windows', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-windows',
127 | ('*', '*', 'EasyMercurial'): 'http://easyhg.org/download.html',
128 | ('*', '*', 'argparse'): 'https://pypi.python.org/pypi/argparse#installation',
129 | ('*', '*', 'ash'): 'http://www.in-ulm.de/~mascheck/various/ash/',
130 | ('*', '*', 'bash'): 'http://www.gnu.org/software/bash/manual/html_node/Basic-Installation.html#Basic-Installation',
131 | ('Linux', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/LinuxBuildInstructions',
132 | ('Darwin', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/MacBuildInstructions',
133 | ('Windows', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos/build-instructions-windows',
134 | ('*', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos',
135 | ('Windows', '*', 'emacs'): 'http://www.gnu.org/software/emacs/windows/Installing-Emacs.html',
136 | ('*', '*', 'emacs'): 'http://www.gnu.org/software/emacs/#Obtaining',
137 | ('*', '*', 'firefox'): 'http://www.mozilla.org/en-US/firefox/new/',
138 | ('Linux', '*', 'gedit'): 'http://www.linuxfromscratch.org/blfs/view/svn/gnome/gedit.html',
139 | ('*', '*', 'git'): 'http://git-scm.com/downloads',
140 | ('*', '*', 'google-chrome'): 'https://www.google.com/intl/en/chrome/browser/',
141 | ('*', '*', 'hg'): 'http://mercurial.selenic.com/',
142 | ('*', '*', 'mercurial'): 'http://mercurial.selenic.com/',
143 | ('*', '*', 'IPython'): 'http://ipython.org/install.html',
144 | ('*', '*', 'ipython'): 'http://ipython.org/install.html',
145 | ('*', '*', 'jinja'): 'http://jinja.pocoo.org/docs/intro/#installation',
146 | ('*', '*', 'kate'): 'http://kate-editor.org/get-it/',
147 | ('*', '*', 'make'): 'http://www.gnu.org/software/make/',
148 | ('Darwin', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#building-on-osx',
149 | ('Windows', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing-on-windows',
150 | ('*', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing',
151 | ('*', '*', 'mayavi.mlab'): 'http://docs.enthought.com/mayavi/mayavi/installation.html',
152 | ('*', '*', 'nano'): 'http://www.nano-editor.org/dist/latest/faq.html#3',
153 | ('*', '*', 'networkx'): 'http://networkx.github.com/documentation/latest/install.html#installing',
154 | ('*', '*', 'nose'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
155 | ('*', '*', 'nosetests'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
156 | ('*', '*', 'notepad++'): 'http://notepad-plus-plus.org/download/v6.3.html',
157 | ('*', '*', 'numpy'): 'http://docs.scipy.org/doc/numpy/user/install.html',
158 | ('*', '*', 'pandas'): 'http://pandas.pydata.org/pandas-docs/stable/install.html',
159 | ('*', '*', 'pip'): 'http://www.pip-installer.org/en/latest/installing.html',
160 | ('*', '*', 'pytest'): 'http://pytest.org/latest/getting-started.html',
161 | ('*', '*', 'python'): 'http://www.python.org/download/releases/2.7.3/#download',
162 | ('*', '*', 'pyzmq'): 'https://github.com/zeromq/pyzmq/wiki/Building-and-Installing-PyZMQ',
163 | ('*', '*', 'py.test'): 'http://pytest.org/latest/getting-started.html',
164 | ('Linux', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Linux',
165 | ('Darwin', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Mac_OS_X',
166 | ('Windows', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Windows',
167 | ('*', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy',
168 | ('*', '*', 'setuptools'): 'https://pypi.python.org/pypi/setuptools#installation-instructions',
169 | ('*', '*', 'sqlite3'): 'http://www.sqlite.org/download.html',
170 | ('*', '*', 'sublime-text'): 'http://www.sublimetext.com/2',
171 | ('*', '*', 'sympy'): 'http://docs.sympy.org/dev/install.html',
172 | ('Darwin', '*', 'textmate'): 'http://macromates.com/',
173 | ('Darwin', '*', 'textwrangler'): 'http://www.barebones.com/products/textwrangler/download.html',
174 | ('*', '*', 'tornado'): 'http://www.tornadoweb.org/',
175 | ('*', '*', 'vim'): 'http://www.vim.org/download.php',
176 | ('Darwin', '*', 'xcode'): 'https://developer.apple.com/xcode/',
177 | ('*', '*', 'xemacs'): 'http://www.us.xemacs.org/Install/',
178 | ('*', '*', 'zsh'): 'http://www.zsh.org/',
179 | }
180 |
181 | def _get_message(self):
182 | return self._message
183 | def _set_message(self, message):
184 | self._message = message
185 | message = property(_get_message, _set_message)
186 |
187 | def __init__(self, checker, message, causes=None):
188 | super(DependencyError, self).__init__(message)
189 | self.checker = checker
190 | self.message = message
191 | if causes is None:
192 | causes = []
193 | self.causes = causes
194 |
195 | def get_url(self):
196 | system = _platform.system()
197 | version = None
198 | for pversion in (
199 | 'linux_distribution',
200 | 'mac_ver',
201 | 'win32_ver',
202 | ):
203 | value = getattr(_platform, pversion)()
204 | if value[0]:
205 | version = value[0]
206 | break
207 | package = self.checker.name
208 | for (s,v,p),url in self._setup_urls.items():
209 | if (_fnmatch.fnmatch(system, s) and
210 | _fnmatch.fnmatch(version, v) and
211 | _fnmatch.fnmatch(package, p)):
212 | return url
213 | return self._default_url
214 |
215 | def __str__(self):
216 | url = self.get_url()
217 | lines = [
218 | 'check for {0} failed:'.format(self.checker.full_name()),
219 | ' ' + self.message,
220 | ' For instructions on installing an up-to-date version, see',
221 | ' ' + url,
222 | ]
223 | if self.causes:
224 | lines.append(' causes:')
225 | for cause in self.causes:
226 | lines.extend(' ' + line for line in str(cause).splitlines())
227 | return '\n'.join(lines)
228 |
229 |
230 | def check(checks=None):
231 | successes = []
232 | failures = []
233 | if not checks:
234 | checks = CHECKS
235 | for check in checks:
236 | try:
237 | checker = CHECKER[check]
238 | except KeyError as e:
239 | raise InvalidCheck(check)# from e
240 | _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
241 | try:
242 | version = checker.check()
243 | except DependencyError as e:
244 | failures.append(e)
245 | _sys.stdout.write('fail\n')
246 | else:
247 | _sys.stdout.write('pass\n')
248 | successes.append((checker, version))
249 | if successes:
250 | print('\nSuccesses:\n')
251 | for checker,version in successes:
252 | print('{0} {1}'.format(
253 | checker.full_name(),
254 | version or 'unknown'))
255 | if failures:
256 | print('\nFailures:')
257 | printed = []
258 | for failure in failures:
259 | if failure not in printed:
260 | print()
261 | print(failure)
262 | printed.append(failure)
263 | return False
264 | return True
265 |
266 |
267 | class Dependency (object):
268 | def __init__(self, name, long_name=None, minimum_version=None,
269 | version_delimiter='.', and_dependencies=None,
270 | or_dependencies=None):
271 | self.name = name
272 | self.long_name = long_name or name
273 | self.minimum_version = minimum_version
274 | self.version_delimiter = version_delimiter
275 | if not and_dependencies:
276 | and_dependencies = []
277 | self.and_dependencies = and_dependencies
278 | if not or_dependencies:
279 | or_dependencies = []
280 | self.or_dependencies = or_dependencies
281 | self._check_error = None
282 |
283 | def __str__(self):
284 | return '<{0} {1}>'.format(type(self).__name__, self.name)
285 |
286 | def full_name(self):
287 | if self.name == self.long_name:
288 | return self.name
289 | else:
290 | return '{0} ({1})'.format(self.long_name, self.name)
291 |
292 | def check(self):
293 | if self._check_error:
294 | raise self._check_error
295 | try:
296 | self._check_dependencies()
297 | return self._check()
298 | except DependencyError as e:
299 | self._check_error = e # cache for future calls
300 | raise
301 |
302 | def _check_dependencies(self):
303 | for dependency in self.and_dependencies:
304 | if not hasattr(dependency, 'check'):
305 | dependency = CHECKER[dependency]
306 | try:
307 | dependency.check()
308 | except DependencyError as e:
309 | raise DependencyError(
310 | checker=self,
311 | message=(
312 | 'some dependencies for {0} were not satisfied'
313 | ).format(self.full_name()),
314 | causes=[e])
315 | self.or_pass = None
316 | or_errors = []
317 | for dependency in self.or_dependencies:
318 | if not hasattr(dependency, 'check'):
319 | dependency = CHECKER[dependency]
320 | try:
321 | version = dependency.check()
322 | except DependencyError as e:
323 | or_errors.append(e)
324 | else:
325 | self.or_pass = {
326 | 'dependency': dependency,
327 | 'version': version,
328 | }
329 | break # no need to test other dependencies
330 | if self.or_dependencies and not self.or_pass:
331 | raise DependencyError(
332 | checker=self,
333 | message=(
334 | '{0} requires at least one of the following dependencies'
335 | ).format(self.full_name()),
336 | causes=or_errors)
337 |
338 | def _check(self):
339 | version = self._get_version()
340 | parsed_version = None
341 | if hasattr(self, '_get_parsed_version'):
342 | parsed_version = self._get_parsed_version()
343 | if self.minimum_version:
344 | self._check_version(version=version, parsed_version=parsed_version)
345 | return version
346 |
347 | def _get_version(self):
348 | raise NotImplementedError(self)
349 |
350 | def _minimum_version_string(self):
351 | return self.version_delimiter.join(
352 | str(part) for part in self.minimum_version)
353 |
354 | def _check_version(self, version, parsed_version=None):
355 | if not parsed_version:
356 | parsed_version = self._parse_version(version=version)
357 | if not parsed_version or parsed_version < self.minimum_version:
358 | raise DependencyError(
359 | checker=self,
360 | message='outdated version of {0}: {1} (need >= {2})'.format(
361 | self.full_name(), version, self._minimum_version_string()))
362 |
363 | def _parse_version(self, version):
364 | if not version:
365 | return None
366 | parsed_version = []
367 | for part in version.split(self.version_delimiter):
368 | try:
369 | parsed_version.append(int(part))
370 | except ValueError as e:
371 | raise DependencyError(
372 | checker=self,
373 | message=(
374 | 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
375 | ).format(
376 | part, version, self.full_name(),
377 | self._minimum_version_string()))# from e
378 | return tuple(parsed_version)
379 |
380 |
381 | class PythonDependency (Dependency):
382 | def __init__(self, name='python', long_name='Python version',
383 | minimum_version=(2, 6), **kwargs):
384 | super(PythonDependency, self).__init__(
385 | name=name, long_name=long_name, minimum_version=minimum_version,
386 | **kwargs)
387 |
388 | def _get_version(self):
389 | return _sys.version
390 |
391 | def _get_parsed_version(self):
392 | return _sys.version_info
393 |
394 |
395 | CHECKER['python'] = PythonDependency()
396 |
397 |
398 | class CommandDependency (Dependency):
399 | exe_extension = _distutils_ccompiler.new_compiler().exe_extension
400 |
401 | def __init__(self, command, paths=None, version_options=('--version',),
402 | stdin=None, version_regexp=None, version_stream='stdout',
403 | **kwargs):
404 | if 'name' not in kwargs:
405 | kwargs['name'] = command
406 | super(CommandDependency, self).__init__(**kwargs)
407 | self.command = command
408 | self.paths = paths
409 | self.version_options = version_options
410 | self.stdin = None
411 | if not version_regexp:
412 | regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
413 | version_regexp = _re.compile(regexp)
414 | self.version_regexp = version_regexp
415 | self.version_stream = version_stream
416 |
417 | def _get_command_version_stream(self, command=None, stdin=None,
418 | expect=(0,)):
419 | if command is None:
420 | command = self.command + (self.exe_extension or '')
421 | if not stdin:
422 | stdin = self.stdin
423 | if stdin:
424 | popen_stdin = _subprocess.PIPE
425 | else:
426 | popen_stdin = None
427 | try:
428 | p = _subprocess.Popen(
429 | [command] + list(self.version_options), stdin=popen_stdin,
430 | stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
431 | universal_newlines=True)
432 | except OSError as e:
433 | raise DependencyError(
434 | checker=self,
435 | message="could not find '{0}' executable".format(command),
436 | )# from e
437 | stdout,stderr = p.communicate(stdin)
438 | status = p.wait()
439 | if status not in expect:
440 | lines = [
441 | "failed to execute: {0} {1}".format(
442 | command,
443 | ' '.join(_shlex.quote(arg)
444 | for arg in self.version_options)),
445 | 'status: {0}'.format(status),
446 | ]
447 | for name,string in [('stdout', stdout), ('stderr', stderr)]:
448 | if string:
449 | lines.extend([name + ':', string])
450 | raise DependencyError(checker=self, message='\n'.join(lines))
451 | for name,string in [('stdout', stdout), ('stderr', stderr)]:
452 | if name == self.version_stream:
453 | if not string:
454 | raise DependencyError(
455 | checker=self,
456 | message='empty version stream on {0} for {1}'.format(
457 | self.version_stream, command))
458 | return string
459 | raise NotImplementedError(self.version_stream)
460 |
461 | def _get_version_stream(self, **kwargs):
462 | paths = [self.command + (self.exe_extension or '')]
463 | if self.exe_extension:
464 | paths.append(self.command) # also look at the extension-less path
465 | if self.paths:
466 | paths.extend(self.paths)
467 | or_errors = []
468 | for path in paths:
469 | try:
470 | return self._get_command_version_stream(command=path, **kwargs)
471 | except DependencyError as e:
472 | or_errors.append(e)
473 | raise DependencyError(
474 | checker=self,
475 | message='errors finding {0} version'.format(
476 | self.full_name()),
477 | causes=or_errors)
478 |
479 | def _get_version(self):
480 | version_stream = self._get_version_stream()
481 | match = self.version_regexp.search(version_stream)
482 | if not match:
483 | raise DependencyError(
484 | checker=self,
485 | message='no version string in output:\n{0}'.format(
486 | version_stream))
487 | return match.group(1)
488 |
489 |
490 | def _program_files_paths(*args):
491 | "Utility for generating MS Windows search paths"
492 | pf = _os.environ.get('ProgramFiles', '/usr/bin')
493 | pfx86 = _os.environ.get('ProgramFiles(x86)', pf)
494 | paths = [_os.path.join(pf, *args)]
495 | if pfx86 != pf:
496 | paths.append(_os.path.join(pfx86, *args))
497 | return paths
498 |
499 |
500 | for command,long_name,minimum_version,paths in [
501 | ('sh', 'Bourne Shell', None, None),
502 | ('ash', 'Almquist Shell', None, None),
503 | ('bash', 'Bourne Again Shell', None, None),
504 | ('csh', 'C Shell', None, None),
505 | ('ksh', 'KornShell', None, None),
506 | ('dash', 'Debian Almquist Shell', None, None),
507 | ('tcsh', 'TENEX C Shell', None, None),
508 | ('zsh', 'Z Shell', None, None),
509 | ('git', 'Git', (1, 7, 0), None),
510 | ('hg', 'Mercurial', (2, 0, 0), None),
511 | ('EasyMercurial', None, (1, 3), None),
512 | ('pip', None, None, None),
513 | ('sqlite3', 'SQLite 3', None, None),
514 | ('nosetests', 'Nose', (1, 0, 0), None),
515 | ('ipython', 'IPython script', (1, 0), None),
516 | ('emacs', 'Emacs', None, None),
517 | ('xemacs', 'XEmacs', None, None),
518 | ('vim', 'Vim', None, None),
519 | ('vi', None, None, None),
520 | ('nano', 'Nano', None, None),
521 | ('gedit', None, None, None),
522 | ('kate', 'Kate', None, None),
523 | ('notepad++', 'Notepad++', None,
524 | _program_files_paths('Notepad++', 'notepad++.exe')),
525 | ('firefox', 'Firefox', None,
526 | _program_files_paths('Mozilla Firefox', 'firefox.exe')),
527 | ('google-chrome', 'Google Chrome', None,
528 | _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe')
529 | ),
530 | ('chromium', 'Chromium', None, None),
531 | ]:
532 | if not long_name:
533 | long_name = command
534 | CHECKER[command] = CommandDependency(
535 | command=command, paths=paths, long_name=long_name,
536 | minimum_version=minimum_version)
537 | del command, long_name, minimum_version, paths # cleanup namespace
538 |
539 |
540 | class MakeDependency (CommandDependency):
541 | makefile = '\n'.join([
542 | 'all:',
543 | '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
544 | '\t@echo "MAKE=$(MAKE)"',
545 | '',
546 | ])
547 |
548 | def _get_version(self):
549 | try:
550 | return super(MakeDependency, self)._get_version()
551 | except DependencyError as e:
552 | version_options = self.version_options
553 | self.version_options = ['-f', '-']
554 | try:
555 | stream = self._get_version_stream(stdin=self.makefile)
556 | info = {}
557 | for line in stream.splitlines():
558 | try:
559 | key,value = line.split('=', 1)
560 | except ValueError as ve:
561 | raise e# from NotImplementedError(stream)
562 | info[key] = value
563 | if info.get('MAKE_VERSION', None):
564 | return info['MAKE_VERSION']
565 | elif info.get('MAKE', None):
566 | return None
567 | raise e
568 | finally:
569 | self.version_options = version_options
570 |
571 |
572 | CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
573 |
574 |
575 | class EasyInstallDependency (CommandDependency):
576 | def _get_version(self):
577 | try:
578 | return super(EasyInstallDependency, self)._get_version()
579 | except DependencyError as e:
580 | version_stream = self.version_stream
581 | try:
582 | self.version_stream = 'stderr'
583 | stream = self._get_version_stream(expect=(1,))
584 | if 'option --version not recognized' in stream:
585 | return 'unknown (possibly Setuptools?)'
586 | finally:
587 | self.version_stream = version_stream
588 |
589 |
590 | CHECKER['easy_install'] = EasyInstallDependency(
591 | command='easy_install', long_name='Setuptools easy_install',
592 | minimum_version=None)
593 |
594 |
595 | CHECKER['py.test'] = CommandDependency(
596 | command='py.test', version_stream='stderr',
597 | minimum_version=None)
598 |
599 |
600 | class PathCommandDependency (CommandDependency):
601 | """A command that doesn't support --version or equivalent options
602 |
603 | On some operating systems (e.g. OS X), a command's executable may
604 | be hard to find, or not exist in the PATH. Work around that by
605 | just checking for the existence of a characteristic file or
606 | directory. Since the characteristic path may depend on OS,
607 | installed version, etc., take a list of paths, and succeed if any
608 | of them exists.
609 | """
610 | def _get_command_version_stream(self, *args, **kwargs):
611 | raise NotImplementedError()
612 |
613 | def _get_version_stream(self, *args, **kwargs):
614 | raise NotImplementedError()
615 |
616 | def _get_version(self):
617 | for path in self.paths:
618 | if _os.path.exists(path):
619 | return None
620 | raise DependencyError(
621 | checker=self,
622 | message=(
623 | 'nothing exists at any of the expected paths for {0}:\n {1}'
624 | ).format(
625 | self.full_name(),
626 | '\n '.join(p for p in self.paths)))
627 |
628 |
629 | for paths,name,long_name in [
630 | ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app')],
631 | 'sublime-text', 'Sublime Text'),
632 | ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app')],
633 | 'textmate', 'TextMate'),
634 | ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app')],
635 | 'textwrangler', 'TextWrangler'),
636 | ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app')],
637 | 'safari', 'Safari'),
638 | ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app'), # OS X >=1.7
639 | _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app'
640 | ) # OS X 1.6,
641 | ],
642 | 'xcode', 'Xcode'),
643 | ]:
644 | if not long_name:
645 | long_name = name
646 | CHECKER[name] = PathCommandDependency(
647 | command=None, paths=paths, name=name, long_name=long_name)
648 | del paths, name, long_name # cleanup namespace
649 |
650 |
651 | class PythonPackageDependency (Dependency):
652 | def __init__(self, package, **kwargs):
653 | if 'name' not in kwargs:
654 | kwargs['name'] = package
655 | if 'and_dependencies' not in kwargs:
656 | kwargs['and_dependencies'] = []
657 | if 'python' not in kwargs['and_dependencies']:
658 | kwargs['and_dependencies'].append('python')
659 | super(PythonPackageDependency, self).__init__(**kwargs)
660 | self.package = package
661 |
662 | def _get_version(self):
663 | package = self._get_package(self.package)
664 | return self._get_version_from_package(package)
665 |
666 | def _get_package(self, package):
667 | try:
668 | return _importlib.import_module(package)
669 | except ImportError as e:
670 | raise DependencyError(
671 | checker=self,
672 | message="could not import the '{0}' package for {1}".format(
673 | package, self.full_name()),
674 | )# from e
675 |
676 | def _get_version_from_package(self, package):
677 | try:
678 | version = package.__version__
679 | except AttributeError:
680 | version = None
681 | return version
682 |
683 |
684 | for package,name,long_name,minimum_version,and_dependencies in [
685 | ('nose', None, 'Nose Python package',
686 | CHECKER['nosetests'].minimum_version, None),
687 | ('pytest', None, 'pytest Python package',
688 | CHECKER['py.test'].minimum_version, None),
689 | ('jinja2', 'jinja', 'Jinja', (2, 6), None),
690 | ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
691 | ('IPython', None, 'IPython Python package',
692 | CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']),
693 | ('argparse', None, 'Argparse', None, None),
694 | ('numpy', None, 'NumPy', None, None),
695 | ('scipy', None, 'SciPy', None, None),
696 | ('matplotlib', None, 'Matplotlib', None, None),
697 | ('pandas', None, 'Pandas', (0, 8), None),
698 | ('sympy', None, 'SymPy', None, None),
699 | ('Cython', None, None, None, None),
700 | ('networkx', None, 'NetworkX', None, None),
701 | ('mayavi.mlab', None, 'MayaVi', None, None),
702 | ('setuptools', None, 'Setuptools', None, None),
703 | ]:
704 | if not name:
705 | name = package
706 | if not long_name:
707 | long_name = name
708 | kwargs = {}
709 | if and_dependencies:
710 | kwargs['and_dependencies'] = and_dependencies
711 | CHECKER[name] = PythonPackageDependency(
712 | package=package, name=name, long_name=long_name,
713 | minimum_version=minimum_version, **kwargs)
714 | # cleanup namespace
715 | del package, name, long_name, minimum_version, and_dependencies, kwargs
716 |
717 |
718 | class MercurialPythonPackage (PythonPackageDependency):
719 | def _get_version(self):
720 | try: # mercurial >= 1.2
721 | package = _importlib.import_module('mercurial.util')
722 | except ImportError as e: # mercurial <= 1.1.2
723 | package = self._get_package('mercurial.version')
724 | return package.get_version()
725 | else:
726 | return package.version()
727 |
728 |
729 | CHECKER['mercurial'] = MercurialPythonPackage(
730 | package='mercurial.util', name='mercurial',
731 | long_name='Mercurial Python package',
732 | minimum_version=CHECKER['hg'].minimum_version)
733 |
734 |
735 | class TornadoPythonPackage (PythonPackageDependency):
736 | def _get_version_from_package(self, package):
737 | return package.version
738 |
739 | def _get_parsed_version(self):
740 | package = self._get_package(self.package)
741 | return package.version_info
742 |
743 |
744 | CHECKER['tornado'] = TornadoPythonPackage(
745 | package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
746 |
747 |
748 | class SQLitePythonPackage (PythonPackageDependency):
749 | def _get_version_from_package(self, package):
750 | return _sys.version
751 |
752 | def _get_parsed_version(self):
753 | return _sys.version_info
754 |
755 |
756 | CHECKER['sqlite3-python'] = SQLitePythonPackage(
757 | package='sqlite3', name='sqlite3-python',
758 | long_name='SQLite Python package',
759 | minimum_version=CHECKER['sqlite3'].minimum_version)
760 |
761 |
762 | class UserTaskDependency (Dependency):
763 | "Prompt the user to complete a task and check for success"
764 | def __init__(self, prompt, **kwargs):
765 | super(UserTaskDependency, self).__init__(**kwargs)
766 | self.prompt = prompt
767 |
768 | def _check(self):
769 | if _sys.version_info >= (3, ):
770 | result = input(self.prompt)
771 | else: # Python 2.x
772 | result = raw_input(self.prompt)
773 | return self._check_result(result)
774 |
775 | def _check_result(self, result):
776 | raise NotImplementedError()
777 |
778 |
779 | class EditorTaskDependency (UserTaskDependency):
780 | def __init__(self, **kwargs):
781 | self.path = _os.path.expanduser(_os.path.join(
782 | '~', 'swc-installation-test.txt'))
783 | self.contents = 'Hello, world!'
784 | super(EditorTaskDependency, self).__init__(
785 | prompt=(
786 | 'Open your favorite text editor and create the file\n'
787 | ' {0}\n'
788 | 'containing the line:\n'
789 | ' {1}\n'
790 | 'Press enter here after you have done this.\n'
791 | 'You may remove the file after you have finished testing.'
792 | ).format(self.path, self.contents),
793 | **kwargs)
794 |
795 | def _check_result(self, result):
796 | message = None
797 | try:
798 | with open(self.path, 'r') as f:
799 | contents = f.read()
800 | except IOError as e:
801 | raise DependencyError(
802 | checker=self,
803 | message='could not open {0!r}: {1}'.format(self.path, e)
804 | )# from e
805 | if contents.strip() != self.contents:
806 | raise DependencyError(
807 | checker=self,
808 | message=(
809 | 'file contents ({0!r}) did not match the expected {1!r}'
810 | ).format(contents, self.contents))
811 |
812 |
813 | CHECKER['other-editor'] = EditorTaskDependency(
814 | name='other-editor', long_name='')
815 |
816 |
817 | class VirtualDependency (Dependency):
818 | def _check(self):
819 | return '{0} {1}'.format(
820 | self.or_pass['dependency'].full_name(),
821 | self.or_pass['version'])
822 |
823 |
824 | for name,long_name,dependencies in [
825 | ('virtual-shell', 'command line shell', (
826 | 'bash',
827 | 'dash',
828 | 'ash',
829 | 'zsh',
830 | 'ksh',
831 | 'csh',
832 | 'tcsh',
833 | 'sh',
834 | )),
835 | ('virtual-editor', 'text/code editor', (
836 | 'emacs',
837 | 'xemacs',
838 | 'vim',
839 | 'vi',
840 | 'nano',
841 | 'gedit',
842 | 'kate',
843 | 'notepad++',
844 | 'sublime-text',
845 | 'textmate',
846 | 'textwrangler',
847 | 'other-editor', # last because it requires user interaction
848 | )),
849 | ('virtual-browser', 'web browser', (
850 | 'firefox',
851 | 'google-chrome',
852 | 'chromium',
853 | 'safari',
854 | )),
855 | ('virtual-pypi-installer', 'PyPI installer', (
856 | 'pip',
857 | 'easy_install',
858 | )),
859 | ]:
860 | CHECKER[name] = VirtualDependency(
861 | name=name, long_name=long_name, or_dependencies=dependencies)
862 | del name, long_name, dependencies # cleanup namespace
863 |
864 |
865 | def _print_info(key, value, indent=19):
866 | print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
867 |
868 | def print_system_info():
869 | print("If you do not understand why the above failures occurred,")
870 | print("copy and send the *entire* output (all info above and summary")
871 | print("below) to the instructor for help.")
872 | print()
873 | print('==================')
874 | print('System information')
875 | print('==================')
876 | _print_info('os.name', _os.name)
877 | _print_info('os.uname', _platform.uname())
878 | _print_info('platform', _sys.platform)
879 | _print_info('platform+', _platform.platform())
880 | for pversion in (
881 | 'linux_distribution',
882 | 'mac_ver',
883 | 'win32_ver',
884 | ):
885 | value = getattr(_platform, pversion)()
886 | if value[0]:
887 | _print_info(pversion, value)
888 | _print_info('prefix', _sys.prefix)
889 | _print_info('exec_prefix', _sys.exec_prefix)
890 | _print_info('executable', _sys.executable)
891 | _print_info('version_info', _sys.version_info)
892 | _print_info('version', _sys.version)
893 | _print_info('environment', '')
894 | for key,value in sorted(_os.environ.items()):
895 | print(' {0}={1}'.format(key, value))
896 | print('==================')
897 |
898 | def print_suggestions(instructor_fallback=True):
899 | print()
900 | print('For suggestions on installing missing packages, see')
901 | print('http://software-carpentry.org/setup/')
902 | print('')
903 | print('For instructings on installing a particular package,')
904 | print('see the failure message for that package printed above.')
905 | if instructor_fallback:
906 | print('')
907 | print('For help, email the *entire* output of this script to')
908 | print('your instructor.')
909 |
910 |
911 | if __name__ == '__main__':
912 | import optparse as _optparse
913 |
914 | parser = _optparse.OptionParser(usage='%prog [options] [check...]')
915 | epilog = __doc__
916 | parser.format_epilog = lambda formatter: '\n' + epilog
917 | parser.add_option(
918 | '-v', '--verbose', action='store_true',
919 | help=('print additional information to help troubleshoot '
920 | 'installation issues'))
921 | options,args = parser.parse_args()
922 | try:
923 | passed = check(args)
924 | except InvalidCheck as e:
925 | print("I don't know how to check for {0!r}".format(e.check))
926 | print('I do know how to check for:')
927 | for key,checker in sorted(CHECKER.items()):
928 | if checker.long_name != checker.name:
929 | print(' {0} {1}({2})'.format(
930 | key, ' '*(20-len(key)), checker.long_name))
931 | else:
932 | print(' {0}'.format(key))
933 | _sys.exit(1)
934 | if not passed:
935 | if options.verbose:
936 | print()
937 | print_system_info()
938 | print_suggestions(instructor_fallback=True)
939 | _sys.exit(1)
940 |
--------------------------------------------------------------------------------