diff --git a/.gitignore b/.gitignore index 053ae06c..e6c77b9d 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ instance/ # Sphinx documentation docs/_build/ +source/_build/ # PyBuilder target/ @@ -104,4 +105,7 @@ ENV/ .mypy_cache/ # emacs -*.*~ \ No newline at end of file +*.*~ + +# vscode +.vscode/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..b858b1ac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +dist: trusty + +language: python + +python: "3.6" + +cache: pip + +install: "pip install -r requirements.txt" + +script: + - make html + +deploy: + provider: pages + skip-cleanup: true + github-token: $GITHUB_TOKEN + keep-history: true + local-dir: build/html + on: + branch: master + target-branch: gh-pages diff --git a/README.rst b/README.rst index eea28014..9f08477b 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ Building the docs for uploading to gh-pages The html docs are published by gitHub's gh-pages. This is accomplished by putting all the html in the ``gh-pages`` branch of the repo. -YOu can do that by hand, by copying any new html to the branch, but if you want to do it more often, it's easier to automate it. +You can do that by hand, by copying any new html to the branch, but if you want to do it more often, it's easier to automate it. There is a bash script that will do it for you: ``build_gh_pages.sh``. @@ -54,7 +54,7 @@ It requires a bit of setup: git pull -* Once this is setup, you can run the build_gh_pages script from the name repo, and it should: +* Once this is setup, you can run the build_gh_pages script from the main repo, and it should: - build the html docs - copy that build docs over to the other clone diff --git a/build_gh_pages.sh b/build_gh_pages.sh index d5d3047c..6a8a84c8 100755 --- a/build_gh_pages.sh +++ b/build_gh_pages.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # simple script to build and push to gh-pages # designed to be run from master diff --git a/requirements.txt b/requirements.txt index 29a37631..1b6df6a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -sphinx +sphinx==1.7.7 sphinx-rtd-theme ipython diff --git a/source/class_schedule/session_1_01.rst b/source/class_schedule/session_1_01.rst index 7d924862..85598b2e 100644 --- a/source/class_schedule/session_1_01.rst +++ b/source/class_schedule/session_1_01.rst @@ -40,8 +40,6 @@ In-class Activities ../modules/Git - ../modules/lightning_talks - ../modules/BasicPython ../modules/Py2vsPy3 diff --git a/source/class_schedule/session_1_03.rst b/source/class_schedule/session_1_03.rst index 8e635f7a..3b6d9bce 100644 --- a/source/class_schedule/session_1_03.rst +++ b/source/class_schedule/session_1_03.rst @@ -68,7 +68,7 @@ Mailroom Exercises You've now got the basics of the language down -- enough to write the first full "program": -:ref:`exercise_mailroom` +:ref:`exercise_mailroom_part1` Post-class Activites diff --git a/source/class_schedule/session_1_04.rst b/source/class_schedule/session_1_04.rst index bbf2d141..0f07b0e7 100644 --- a/source/class_schedule/session_1_04.rst +++ b/source/class_schedule/session_1_04.rst @@ -49,7 +49,7 @@ Exercises: * :ref:`exercise_file_lab` - * Update mailroom with dicts and files: :ref:`exercise_mailroom_plus` + * Update mailroom with dicts and files: :ref:`exercise_mailroom_part2_dict_files` * :ref:`exercise_trigrams` diff --git a/source/class_schedule/session_1_05.rst b/source/class_schedule/session_1_05.rst index 7f364b83..c4171b8a 100644 --- a/source/class_schedule/session_1_05.rst +++ b/source/class_schedule/session_1_05.rst @@ -36,7 +36,7 @@ Exercises: 3. Optional: :ref:`exercise_exceptions_lab` -4. Update mailroom with Exceptions: :ref:`exercise_mailroom_exceptions` +4. Update mailroom with Exceptions: :ref:`exercise_mailroom_part3_exceptions` Post-class Activities diff --git a/source/class_schedule/session_1_06.rst b/source/class_schedule/session_1_06.rst index 7fefaa75..5d6a037a 100644 --- a/source/class_schedule/session_1_06.rst +++ b/source/class_schedule/session_1_06.rst @@ -53,7 +53,7 @@ Exercises: Testing mailroom: ................. -:ref:`exercise_mailroom_testing` +:ref:`exercise_mailroom_part4_testing` Write a complete set of unit tests for your mailroom program. diff --git a/source/conf.py b/source/conf.py index b27ab256..ee5d2fbe 100644 --- a/source/conf.py +++ b/source/conf.py @@ -63,10 +63,10 @@ "Christy Heaton", "Jon Jacky", "Maria McKinley", - "Andy Miles" + "Andy Miles", "Rick Riehle", "Joseph Schilz", - "Joseph Sheedy" + "Joseph Sheedy", "Hosung Song" ] diff --git a/source/examples/file_exercise/students.txt b/source/examples/file_exercise/students.txt index dc8508d5..a85853a8 100644 --- a/source/examples/file_exercise/students.txt +++ b/source/examples/file_exercise/students.txt @@ -23,8 +23,8 @@ Mitchell, Joni: php, mysql, python Ramone, John: Johnny, rex, db King, Carol: r Waters, Muddy: perl, python -Star, Richard: Ringo, Tom, shell, python, vb -Smith, Patricia: Patti, Gene, python +Star, Richard: Ringo, shell, python, vb +Smith, Patricia: Patti, python Morrison, Jim: fortran, perl, sql, python Marley, Robert: Bob, c, c++, lisp Simon, Paul: bash, python, sql diff --git a/source/examples/testing/cigar_party.py b/source/examples/testing/cigar_party.py deleted file mode 100644 index e6863f46..00000000 --- a/source/examples/testing/cigar_party.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python - -""" -When squirrels get together for a party, they like to have cigars. -A squirrel party is successful when the number of cigars is between -40 and 60, inclusive. Unless it is the weekend, in which case there -is no upper bound on the number of cigars. - -Return True if the party with the given values is successful, -or False otherwise. -""" - - -def cigar_party(cigars, is_weekend): - pass diff --git a/source/examples/testing/test_cigar_party.py b/source/examples/testing/test_cigar_party.py deleted file mode 100644 index 260d5f47..00000000 --- a/source/examples/testing/test_cigar_party.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python - -""" -When squirrels get together for a party, they like to have cigars. -A squirrel party is successful when the number of cigars is between -40 and 60, inclusive. Unless it is the weekend, in which case there -is no upper bound on the number of cigars. - -Return True if the party with the given values is successful, -or False otherwise. -""" - - -# you can change this import to test different versions -from cigar_party import cigar_party -# from cigar_party import cigar_party2 as cigar_party -# from cigar_party import cigar_party3 as cigar_party - - -def test_1(): - assert cigar_party(30, False) is False - - -def test_2(): - assert cigar_party(50, False) is True - - -def test_3(): - assert cigar_party(70, True) is True - - -def test_4(): - assert cigar_party(30, True) is False - - -def test_5(): - assert cigar_party(50, True) is True - - -def test_6(): - assert cigar_party(60, False) is True - - -def test_7(): - assert cigar_party(61, False) is False - - -def test_8(): - assert cigar_party(40, False) is True - - -def test_9(): - assert cigar_party(39, False) is False - - -def test_10(): - assert cigar_party(40, True) is True - - -def test_11(): - assert cigar_party(39, True) is False diff --git a/source/examples/testing/test_random_pytest.py b/source/examples/testing/test_random_pytest.py index 6250d1b4..b9a65afd 100644 --- a/source/examples/testing/test_random_pytest.py +++ b/source/examples/testing/test_random_pytest.py @@ -8,32 +8,46 @@ import pytest -seq = list(range(10)) +example_seq = list(range(10)) + + +def test_choice(): + """ + A choice selected should be in the sequence + """ + element = random.choice(example_seq) + assert (element in example_seq) + + +def test_sample(): + """ + All the items in a sample should be in the sequence + """ + for element in random.sample(example_seq, 5): + assert element in example_seq def test_shuffle(): - # make sure the shuffled sequence does not lose any elements + """ + Make sure a shuffled sequence does not lose any elements + """ + seq = list(range(10)) random.shuffle(seq) - seq.sort() # IFyou comment this out, it will fail, so you can see output + # seq.sort() # If you comment this out, it will fail, so you can see output print("seq:", seq) # only see output if it fails assert seq == list(range(10)) def test_shuffle_immutable(): + """ + Trying to shuffle an immutable sequence raises an Exception + """ with pytest.raises(TypeError): random.shuffle((1, 2, 3)) - -def test_choice(): - element = random.choice(seq) - assert (element in seq) - - -def test_sample(): - for element in random.sample(seq, 5): - assert element in seq - - def test_sample_too_large(): + """ + Trying to sample more than exist should raise an error + """ with pytest.raises(ValueError): - random.sample(seq, 20) + random.sample(example_seq, 20) diff --git a/source/examples/testing/test_random_unitest.py b/source/examples/testing/test_random_unitest.py index f825be5b..b8a1b712 100644 --- a/source/examples/testing/test_random_unitest.py +++ b/source/examples/testing/test_random_unitest.py @@ -8,7 +8,9 @@ def setUp(self): self.seq = list(range(10)) def test_shuffle(self): - # make sure the shuffled sequence does not lose any elements + """ + make sure the shuffled sequence does not lose any elements + """ random.shuffle(self.seq) self.seq.sort() self.assertEqual(self.seq, list(range(10))) diff --git a/source/examples/testing/test_walnut_party.py b/source/examples/testing/test_walnut_party.py new file mode 100644 index 00000000..62f226dc --- /dev/null +++ b/source/examples/testing/test_walnut_party.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +""" +test code for the walnut party example + +Adapted from the "coding bat" site: https://codingbat.com/python + +When squirrels get together for a party, they like to have walnuts. +A squirrel party is successful when the number of walnuts is between +40 and 60, inclusive. Unless it is the weekend, in which case there +is no upper bound on the number of walnuts. + +Return True if the party with the given values is successful, +or False otherwise. +""" + + +# you can change this import to test different versions +from walnut_party import walnut_party +# from walnut_party import walnut_party2 as walnut_party +# from walnut_party import walnut_party3 as walnut_party + + +def test_1(): + assert walnut_party(30, False) is False + + +def test_2(): + assert walnut_party(50, False) is True + + +def test_3(): + assert walnut_party(70, True) is True + + +def test_4(): + assert walnut_party(30, True) is False + + +def test_5(): + assert walnut_party(50, True) is True + + +def test_6(): + assert walnut_party(60, False) is True + + +def test_7(): + assert walnut_party(61, False) is False + + +def test_8(): + assert walnut_party(40, False) is True + + +def test_9(): + assert walnut_party(39, False) is False + + +def test_10(): + assert walnut_party(40, True) is True + + +def test_11(): + assert walnut_party(39, True) is False diff --git a/source/examples/testing/walnut_party.py b/source/examples/testing/walnut_party.py new file mode 100644 index 00000000..9b195b28 --- /dev/null +++ b/source/examples/testing/walnut_party.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +""" +When squirrels get together for a party, they like to have walnuts. +A squirrel party is successful when the number of walnuts is between +40 and 60, inclusive. Unless it is the weekend, in which case there +is no upper bound on the number of walnuts. + +Return True if the party with the given values is successful, +or False otherwise. +""" + + +def walnut_party(walnuts, is_weekend): + pass diff --git a/source/examples/unicode/ICanEatGlass.utf16.txt b/source/examples/unicode/ICanEatGlass.utf16.txt new file mode 100644 index 00000000..24a0858d Binary files /dev/null and b/source/examples/unicode/ICanEatGlass.utf16.txt differ diff --git a/source/examples/unicode/ICanEatGlass.utf8.txt b/source/examples/unicode/ICanEatGlass.utf8.txt new file mode 100644 index 00000000..9ecba2b9 --- /dev/null +++ b/source/examples/unicode/ICanEatGlass.utf8.txt @@ -0,0 +1,23 @@ +I Can Eat Glass: + +And from the sublime to the ridiculous, here is a certain phrase in an assortment of languages: + +Sanskrit: काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥ + +Sanskrit (standard transcription): kācaṃ śaknomyattum; nopahinasti mām. + +Classical Greek: ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει. + +Greek (monotonic): Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. + +Greek (polytonic): Μπορῶ νὰ φάω σπασμένα γυαλιὰ χωρὶς νὰ πάθω τίποτα. + +Latin: Vitrum edere possum; mihi non nocet. + +Old French: Je puis mangier del voirre. Ne me nuit. + +French: Je peux manger du verre, ça ne me fait pas mal. + +Provençal / Occitan: Pòdi manjar de veire, me nafrariá pas. + +Québécois: J'peux manger d'la vitre, ça m'fa pas mal. \ No newline at end of file diff --git a/source/examples/unicode/exception_test.py b/source/examples/unicode/exception_test.py new file mode 100755 index 00000000..975f1df8 --- /dev/null +++ b/source/examples/unicode/exception_test.py @@ -0,0 +1,16 @@ +#!/usr/bin/python + +""" +example for what happens when you pass non-ascii unicode to a Exception +""" + +msg = u'This is an ASCII-compatible unicode message' + +#msg = u'This is an non ASCII\N{EM DASH}compatible unicode message' + +print "\nDo you see this message in the Exception report?\n" +print msg +print + +raise ValueError(msg) + diff --git a/source/examples/unicode/hello_unicode.py b/source/examples/unicode/hello_unicode.py new file mode 100644 index 00000000..6bbad1de --- /dev/null +++ b/source/examples/unicode/hello_unicode.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +hello = 'Hello ' +world = u'世界' + +print hello + world + +print u"It was nice weather today: it reached 80\u00B0" + +print u"Maybe it will reach 90\N{degree sign}" + +print u"It is extremely rare for it ever to reach 100° in Seattle" diff --git a/source/examples/unicode/latin1_test.py b/source/examples/unicode/latin1_test.py new file mode 100644 index 00000000..3990078f --- /dev/null +++ b/source/examples/unicode/latin1_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +""" +An example of using latin-1 as a universal encoding + +latin-1 is a superset of ASCII that is suitable for western european languages. + +Is the most common, and a good default, if you need a one-byte per char encoding +for European text. + +It also has a nice property: + : every byte value from 0 to 255 is avalid charactor + +Thus you will never get an UnicodeDecodeError if +you try to decode arbitrary bytes with latin-1. + +And it can "round-trip" trhough a unicode object. + +This can be useful is you don't know the encoding -- at least it won't break. +It's also useful if you need to work with cobined text+binary data. + + + +""" + +# all the byte values in a bytes (str) object: +all_bytes = ''.join( [chr(i) for i in range(255)] ) + +print type(all_bytes) +print len(all_bytes) + +print "Example value: 20" +print ord(all_bytes[20]) == 20 +print "Example high value: 245" +print ord(all_bytes[245]) == 245 + +# now decode it to a unicode object: +try: + uni = all_bytes.decode() +except UnicodeDecodeError: + print "OOPS: can't decode with default encoding" + +# latin-1 works: +try: + all_uni = all_bytes.decode('latin-1') + print "Yup -- that worked" + print all_uni + print "note that the ASCII subset is the same..." +except UnicodeDecodeError: + print "OOPS: This should have worked!!" + raise + +## now show that it round-trips: +all_bytes2 = all_uni.encode('latin-1') + +if all_bytes2 == all_bytes: + print "yup -- that worked...the values are preserved on the round trip." +else: + print "Hey, that should have worked" + + + + + + + diff --git a/source/examples/unicode/text.utf16 b/source/examples/unicode/text.utf16 new file mode 100644 index 00000000..b80b2efc Binary files /dev/null and b/source/examples/unicode/text.utf16 differ diff --git a/source/examples/unicode/text.utf32 b/source/examples/unicode/text.utf32 new file mode 100644 index 00000000..c5295310 Binary files /dev/null and b/source/examples/unicode/text.utf32 differ diff --git a/source/examples/unicode/text.utf8 b/source/examples/unicode/text.utf8 new file mode 100644 index 00000000..9de18890 --- /dev/null +++ b/source/examples/unicode/text.utf8 @@ -0,0 +1,17 @@ +Origin (in native language) Name (in native language) +Հայաստան Արամ Խաչատրյան + Australia Nicole Kidman + Österreich Johann Strauß + Azərbaycan Vaqif Səmədoğlu + Азәрбајҹан Вагиф Сәмәдоғлу + Azərbaycan Heydər Əliyev + Азәрбајҹан Һејдәр Әлијев + België René Magritte + Belgique René Magritte + Belgien René Magritte + বাংলা সুকুমার রায় + འབྲུག་ཡུལ། མགོན་པོ་རྡོ་རྗེ། + ប្រទេសកម្ពុជា ព្រះពុទ្ឋឃោសាចារ្យជួនណាត +Canada Céline Dion + ᓄᓇᕗᒻᒥᐅᑦ ᓱᓴᓐ ᐊᒡᓗᒃᑲᖅ + \ No newline at end of file diff --git a/source/examples/unicode/unicodify.py b/source/examples/unicode/unicodify.py new file mode 100644 index 00000000..15683ee6 --- /dev/null +++ b/source/examples/unicode/unicodify.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +''' +Decorators to convert all arguments passed to a function or method to +unicode or str, including default arguments + +From: http://axialcorps.com/2014/03/20/unicode-str/ + +''' + + +import sys +import functools +import inspect + +def _convert_arg(arg, from_, conv, enc): + '''Safely convert unicode to string or string to unicode''' + return getattr(arg, conv)(encoding=enc) if isinstance(arg, from_) else arg + +def _wrap_convert(from_type, fn, encoding=None): + '''Decorate a function converting all str arguments to unicode or + vice-versa''' + conv = 'decode' if from_type is str else 'encode' + encoding = encoding or sys.getdefaultencoding() + + # override string defaults using partial + aspec, dflts = inspect.getargspec(fn), {} + if aspec.defaults: + for k,v in zip(aspec.args[-len(aspec.defaults):],aspec.defaults): + dflts[k] = _convert_arg(v, from_type, conv, encoding) + fn = functools.partial(fn, **dflts) + + @functools.wraps(fn.func if isinstance(fn, functools.partial) else fn) + def converted(*args, **kwargs): + args = [_convert_arg(a, from_type, conv, encoding) for a in args] + for k,v in kwargs.iteritems(): + kwargs[k] = _convert_arg(v, from_type, conv, encoding) + return fn(*args, **kwargs) + + return converted + +def unicodify(fn=None, encoding=None): + '''Convert all str arguments to unicode''' + if fn is None: + return functools.partial(unicodify, encoding=encoding) + return _wrap_convert(str, fn, encoding=encoding) + +def stringify(fn=None, encoding=None): + '''Convert all unicode arguments to str''' + if fn is None: + return functools.partial(stringify, encoding=encoding) + return _wrap_convert(unicode, fn, encoding=encoding) + +__all__ = ['unicodify', 'stringify'] \ No newline at end of file diff --git a/source/exercises/args_kwargs_lab.rst b/source/exercises/args_kwargs_lab.rst index 155dc188..0273c287 100644 --- a/source/exercises/args_kwargs_lab.rst +++ b/source/exercises/args_kwargs_lab.rst @@ -1,7 +1,7 @@ .. _exercise_args_kwargs_lab: -Args and Kwargs LAB -=================== +``args`` and ``kwargs`` LAB +=========================== Goal: ----- @@ -12,33 +12,31 @@ If this is all confusing -- you may want to review this: http://stupidpythonideas.blogspot.com/2013/08/arguments-and-parameters.html -Note: +.. note:: -This is not all that clearly specified -- the goal is for you to -experiment with various ways to define and call functions, so you -can understand what's possible, and what happens with each call. -It is also entirely silly, since the function does not do anything -at all, but it will teach you about using parameters effectively. + This is not all that clearly specified -- the goal is for you to + experiment with various ways to define and call functions, so you + can understand what's possible, and what happens with each call. + It is also entirely silly, since the function does not do anything + at all, but it will teach you about using parameters effectively. Test Driven Development? ------------------------ -Since this code isn't really going to do anything, it doesn't make a lot of sense to test it. However, you need to run the code somehow anyway. So this is a good chance to practice test-driven development anyway. +Since this code isn't really going to do anything, it doesn't make a lot of sense to test it. However, you need to run the code somehow. So this is a good chance to practice test-driven development -- even if only as a way to run your code as you write it. -So for each step of the exercise, write a test that calls your function in a particular way, and test that it returns what you expect. In this case, what you will be testing is not really the code -- but rather your own expectations of what the results should be. +For each step of the exercise, write a test that calls your function in a particular way, and test that it returns what you expect. In this case, what you will be testing is not really the code -- but rather your own expectations of what the results should be. You will also be testing Python's argument handling, which you can be pretty sure DOES work correctly. -So while these won't be useful tests in the usual sense, this is a chance to practice test driven development. +So while these won't be useful tests in the usual sense, this is a chance to get used to test driven development. Procedure --------- -We are going to do this as test driven development. So your first task for -each assignment below is to write a test that will ensure your code does what -we are telling you it should do. +We are going to do this as test driven development: Your first task for each step below is to write a test that will ensure your code does whatvwe are telling you it should do. **Keyword arguments:** @@ -49,15 +47,15 @@ we are telling you it should do. - `link_color` - `visited_color` -* Have it return the colors (use strings for the colors) +* Have it return the colors (use strings for the colors, e.g. "blue", "red", etc.) -* Call it with a couple different parameters set. IOW, write tests that verify that all of the following work as advertised: +* Call it with a couple different parameters set. That is, write tests that verify that all of the following work as advertised: - - using just positional arguments: + - Using just positional arguments: - ``func('red', 'blue', 'yellow', 'chartreuse')`` - - using just keyword arguments: + - Using just keyword arguments: - ``func(link_color='red', back_color='blue')`` @@ -80,10 +78,11 @@ we are telling you it should do. ``*args`` and ``**kwargs`` -* Have it return the colors (use strings for the colors) +* Have it return the colors (use strings for the colors again) * Call it with the same various combinations of arguments used above. -* Also have it print `args` and `kwargs` directly, so you can be sure you understand what's going on. +* Also have it print ``args`` and ``kwargs`` directly, so you can be sure you understand what's going on. * Note that in general, you can't know what will get passed into ``**kwargs`` So maybe adapt your function to be able to do something reasonable with any keywords. + diff --git a/source/exercises/circle_class.rst b/source/exercises/circle_class.rst index a94310d5..b2c47af9 100644 --- a/source/exercises/circle_class.rst +++ b/source/exercises/circle_class.rst @@ -32,8 +32,7 @@ You will use: - properties. - a bunch of "magic methods". - - a classmethod (after you've learned about them...). - + - a classmethod. General Instructions: --------------------- @@ -121,25 +120,25 @@ The user should not be able to set the area: Step 5: ------- -**NOTE:** wait on this one 'till we learn about class methods.. - Add an "alternate constructor" that lets the user create a Circle directly with the diameter: .. code-block:: python >> c = Circle.from_diameter(8) - >> print c.diameter + >> print(c.diameter) 8 - >> print c.radius + >> print(c.radius) 4 +Hint: This is a good use case for a ``classmethod`` + Step 6: ------- Every class should have a nice way to print it out... -Add __str__ and __repr__ methods to your Circle class. +Add ``__str__`` and ``__repr__`` methods to your Circle class. Now you can print it: @@ -147,7 +146,7 @@ Now you can print it: In [2]: c = Circle(4) - In [3]: print c + In [3]: print(c) Circle with radius: 4.000000 In [4]: repr(c) @@ -214,9 +213,6 @@ Once the comparing is done, you should be able to sort a list of circles: In [18]: print circles [Circle(6), Circle(7), Circle(8), Circle(4), Circle(0), Circle(2), Circle(3), Circle(5), Circle(9), Circle(1)] - In [19]: circl - circle circle.py circle.pyc circles - In [19]: circles.sort() In [20]: print circles diff --git a/source/exercises/context-managers-exercise.rst b/source/exercises/context-managers-exercise.rst index 2c826466..67e2b505 100644 --- a/source/exercises/context-managers-exercise.rst +++ b/source/exercises/context-managers-exercise.rst @@ -22,7 +22,7 @@ run all the code inside the context: ...: This code took 0.206805 seconds -NOTE: the time module has what you need: +NOTE: the ``time`` module has what you need: .. code-block:: python @@ -30,7 +30,7 @@ NOTE: the time module has what you need: start = time.clock() # some code here - elapsed = time.clock() = start + elapsed = time.clock() - start ``time.clock()`` returns the number of seconds that this process has been running. You can also use ``time.time()``, which gives the "wall time", rather than the process time. ``time()`` will vary more depending on how busy the system is. But you may want to use it if you want to measure how long it takes to download something, for instance. @@ -38,13 +38,13 @@ Extra Credit ------------ Allow the ``Timer`` context manager to take a file-like -object as an argument (the default should be sys.stdout). The results of the +object as an argument (the default should be ``sys.stdout``). The results of the timing should be printed to the file-like object. You could also pass in a name for this particular context, so the message in the file-like object is labeled -- kind of a poor man's logging system. Extra Extra Credit ------------------ -Implement this a a generator, wrapped by the: +Implement this as a generator, wrapped by the: ``contextlib.contextmanager`` @@ -101,7 +101,7 @@ tests fail when an assert fails: assert some_expression, "a message" -you get a failure when some_expression evaluates as false. +you get a failure when ``some_expression`` evaluates as false. This is more-or-less the same as this code: @@ -110,7 +110,7 @@ This is more-or-less the same as this code: if some_expression: raise AssertionError("a message") -The reason it exists is not so much to save a bit of typing (though that's nice), but that assertions are designed for tests, ans thus can be turned of for an entire python process -- and, indeed are turned off when you turn on optimization. +The reason it exists is not so much to save a bit of typing (though that's nice), but that assertions are designed for tests, and thus can be turned off for an entire python process -- and, indeed are turned off when you turn on optimization. So in your context manager, you can raise an AssertionError, or force one with an assert: diff --git a/source/exercises/dict_lab.rst b/source/exercises/dict_lab.rst index 12ae4ab4..9c863c54 100644 --- a/source/exercises/dict_lab.rst +++ b/source/exercises/dict_lab.rst @@ -15,7 +15,7 @@ Learn the basic ins and outs of Python dictionaries and sets. Procedure --------- -In your student dir in the class repo, create a ``session04`` dir and put in a new ``dict_lab.py`` file. +In your student dir in the class repo, create a ``lesson04`` dir and put in a new ``dict_lab.py`` file. The file should be an executable Python script. That is to say that one should be able to run the script directly like so: @@ -28,6 +28,8 @@ should be able to run the script directly like so: To make this work you, make sure you include the 'shebang' on the first line of your file. +.. code-block:: bash + #!/usr/bin/env python3 @@ -74,11 +76,18 @@ Dictionaries 2 * Using the dictionary from item 1: Make a dictionary using the same keys but with the number of 't's in each value as the value (consider upper and lower case?). + The result should look something like:: + + {"name": 0 + "city": 2 + "cake": 2 + } + Sets ---- * Create sets s2, s3 and s4 that contain numbers from zero through twenty, - divisible by 2, 3 and 4. + divisible by 2, 3 and 4 (figure out a way to compute those -- don't just type them in). * Display the sets. @@ -93,5 +102,5 @@ Sets 2 * Create a frozenset with the letters in 'marathon'. -* display the union and intersection of the two sets. +* Display the union and intersection of the two sets. diff --git a/source/exercises/except_exercise.rst b/source/exercises/except_exercise.rst index 62104ccf..c4b0fdc1 100644 --- a/source/exercises/except_exercise.rst +++ b/source/exercises/except_exercise.rst @@ -9,7 +9,7 @@ This is a little exercise that shows you how to handle exceptions in a way that Procedure ========= -Here are two files that you should put in your ``session05`` directory in the class repo. +Here are two files that you should put in your ``lesson05`` directory in the class repo. :download:`except_exercise.py` diff --git a/source/exercises/fib_and_lucas.rst b/source/exercises/fib_and_lucas.rst index 189aaa29..b0243936 100644 --- a/source/exercises/fib_and_lucas.rst +++ b/source/exercises/fib_and_lucas.rst @@ -12,12 +12,15 @@ Goal: The `Fibonacci Series`_ is a numeric series starting with the integers 0 and 1. -In this series, the next integer is determined by summing the previous two. +In this series, the next integer is determined by summing the previous two + This gives us:: 0, 1, 1, 2, 3, 5, 8, 13, ... +.. note: 0+1 is 1; 1+1 is 2; 1+2 is 3; 2+3 is 5; 3+5 is 8; and so on forever... + We will write a function that computes this series -- then generalize it. .. _Fibonacci Series: http://en.wikipedia.org/wiki/Fibbonaci_Series @@ -25,18 +28,17 @@ We will write a function that computes this series -- then generalize it. Step 1 ------ -* Create a new module ``series.py`` in the ``session02`` folder in your student folder. +* Create a new module ``series.py`` in the ``lesson02`` folder in your student folder. - In it, add a function called ``fibonacci``. - The function should have one parameter ``n``. - - The function should return the ``nth`` value in the fibonacci series - (starting with zero index). + - The function should return the ``nth`` value in the fibonacci series (starting with zero index). * Ensure that your function has a well-formed ``docstring`` -Note that the fibinacci series is naturally recursive -- the value is +Note that the fibonacci series is naturally recursive -- the value is defined by previous values: fib(n) = fib(n-2) + fib(n-1) @@ -58,50 +60,58 @@ In your ``series.py`` module, add a new function ``lucas`` that returns the Ensure that your function has a well-formed ``docstring`` +YOu should find it's *very* similar to the ``fibonacci()`` function. + Generalizing ------------ -Both the *fibonacci series* and the *lucas numbers* are based on an identical -formula. +Both the *fibonacci series* and the *lucas numbers* are based on an identical formula: + +fib(n) = fib(n-2) + fib(n-1) + +That's why the code is so similar. + +This formula creates a class of series that are all related -- each with a different two starting numbers. + +Add a third function called ``sum_series`` that can compute all of these related series. + +It should have one required parameter and two optional parameters. +The required parameter will determine which element in the +series to print. +The two optional parameters will have default values of 0 and 1 and will determine the first two values for the series to be produced. + +Calling this function with no optional parameters will produce numbers from the *fibonacci series* (because 0 and 1 are the defaults). -Add a third function called ``sum_series`` with one required parameter and two -optional parameters. The required parameter will determine which element in the -series to print. The two optional parameters will have default values of 0 and -1 and will determine the first two values for the series to be produced. +Calling it with the optional arguments 2 and 1 will +produce values from the *lucas numbers*. -Calling this function with no optional parameters will produce numbers from the -*fibonacci series*. Calling it with the optional arguments 2 and 1 will -produce values from the *lucas numbers*. Other values for the optional -parameters will produce other series. +Other values for the optional parameters will produce other series. **Note:** While you *could* check the input arguments, and then call one of the functions you wrote, the idea of this exercise is to make a general -function, rather than one specialized. So you should reimplement the code +function, rather than one specialized. So you should re-implement the code in this function. -In fact, you could go back and reimplement your fibonacci and lucas -functions to call this one with particular arguments. +In fact, you could go back and re-implement your fibonacci and lucas +functions to call ``sum-series`` with particular arguments. Ensure that your function has a well-formed ``docstring`` Tests... -------- -Add a block of code to the end of your ``series.py`` -module. Use the block to write a series of ``assert`` statements that +Add a block of code to the end of your ``series.py`` module. +Use the block to write a series of ``assert`` statements that demonstrate that your three functions work properly. Use comments in this block to inform the observer what your tests do. -We have created a template for you to use, to clarify what we mean by these -tests: +We have created a template for you to use to clarify what we mean by these asserts: :download:`series_template.py <../exercises/series_template.py>` -Add your new module to your git clone and commit frequently while working on -your implementation. Include good commit messages that explain concisely both -*what* you are doing and *why*. +Add your new module to your personal git repo and commit frequently while working on your implementation. +Include good commit messages that explain concisely both *what* you are doing and *why*. -When you are finished, push your changes to your fork of the class repository -in GitHub and make a pull request. +When you are finished, push your changes to your fork of the class repository in GitHub and make a pull request. diff --git a/source/exercises/file_lab.rst b/source/exercises/file_lab.rst index a7b26bb3..9aa4dfd9 100644 --- a/source/exercises/file_lab.rst +++ b/source/exercises/file_lab.rst @@ -16,20 +16,22 @@ Paths and File Processing ========================= * Write a program which prints the full path for all files in the current - directory, one per line + directory, one per line. Use either the ``os`` module or ``pathlib``. * Write a program which copies a file from a source, to a destination - (without using shutil, or the OS copy command). - - - Advanced: make it work for any size file: i.e. don't read the entire - contents of the file into memory at once. + (without using shutil, or the OS copy command (you are essentially writing a simple version of the OS copy command)). - This should work for any kind of file, so you need to open the files in binary mode: ``open(filename, 'rb')`` (or ``'wb'`` for writing). Note that for binary files, you can't use ``readline()`` -- lines don't have any meaning for binary files. - - Test it with both text and binary files (maybe jpeg or something of your choosing). + - Test it with both text and binary files (maybe a jpeg or something of your choosing). + + - Advanced: make it work for any size file: i.e. don't read the entire + contents of the file into memory at once. + + - This should only be a few lines of code :-) File reading and parsing @@ -41,9 +43,9 @@ Download this text file: In it, you will find a list of names and what programming languages they have used in the past. This may be similar to a list generated at the beginning of this class. -Write a little script that reads that file, and generates a list of all the languages that have been used. +Write a little script that reads that file and generates a list of all the languages that have been used. -What might be the best data structure to use to keep track of bunch of values without duplication? +What might be the best data structure to use to keep track of bunch of values (the languages) without duplication? The file format: ---------------- @@ -60,5 +62,7 @@ So a colon after the name, then the nickname, and then one or more languages. However, like real data files, the file is NOT well-formed. Only some lines have nicknames, and other small differences, so you will need to write some code to make sure you get it all correct. +How can you tell the difference between a nickname and a language? + Extra challenge: keep track of how many students specified each language. diff --git a/source/exercises/grid_printer.rst b/source/exercises/grid_printer.rst index 861869ff..87804fdc 100644 --- a/source/exercises/grid_printer.rst +++ b/source/exercises/grid_printer.rst @@ -128,7 +128,7 @@ One of the points of writing functions is so you can write code that does simila Write a function ``print_grid(n)`` that takes one integer argument and prints a grid just like before, *BUT* the size of the grid is given by the argument. -For example, ``print_grid(11)`` prints the grid at the top of this page. +For example, ``print_grid(9)`` prints the grid at the top of this page. ``print_grid(3)`` would print a smaller grid:: diff --git a/source/exercises/html_renderer.rst b/source/exercises/html_renderer.rst index 8175b30b..8761e90d 100644 --- a/source/exercises/html_renderer.rst +++ b/source/exercises/html_renderer.rst @@ -30,7 +30,7 @@ If you don't know html -- just look at the example and copy that. And you can re The exercise is broken down into a number of steps -- each requiring a few more OO concepts in Python. -The goal of the code is render html. The goal of the *exercise* is to build up a simple object hierarchy with: +The goal of the code is to render html. The goal of the *exercise* is to build up a simple object hierarchy with: * classes * class attributes @@ -219,7 +219,7 @@ Part B: Now it gets fun! -Now that you have multipel types of elements, it's worth looking a bit at how html works. A given element can hold text, but it can *also* hold other elements. So we need to update our ``Element`` classes to support that. +Now that you have multiple types of elements, it's worth looking a bit at how html works. A given element can hold text, but it can *also* hold other elements. So we need to update our ``Element`` classes to support that. Extend the ``Element.render()`` method so that it can render other elements inside the tag in addition to strings. A recursion-like approach should do it. i.e. it can call the ``render()`` method of the elements it contains. diff --git a/source/exercises/html_renderer_tutorial.rst b/source/exercises/html_renderer_tutorial.rst index d2459ffa..762688c0 100644 --- a/source/exercises/html_renderer_tutorial.rst +++ b/source/exercises/html_renderer_tutorial.rst @@ -252,7 +252,7 @@ In this case, the "html" part is stored in a class attribute. So how would you m "<{}>".format(self.tag) -and +and:: "{}>".format(self.tag) @@ -441,7 +441,7 @@ Darn! Something is wrong here. And this time it errored out before it even got r It failed when we tried to write to the file. We're trying to write a piece of content, and we got a ``NoneType``. How in the world did a ``NoneType`` (which is the type of None) get in there? -Where does the ``self.contents`` list get created? In the ``__init__``. Let's do a little print debugging here. Add a print to the __init__: +Where does the ``self.contents`` list get created? In the ``__init__``. Let's do a little print debugging here. Add a print to the ``__init__``: .. code-block:: python @@ -460,9 +460,9 @@ And run the tests again:: ====================== 1 failed, 3 passed in 0.06 seconds ====================== -Same failure -- but pytest does a nice job of showing you what was printed (stdout) when a test fails. So in this case, at the end of the ``__init__`` method, the contents list looks like ``[None]`` -- a list with a single None object in it. No wonder it failed later when we tried to write that None to a file! +Same failure -- but pytest does a nice job of showing you what was printed (stdout) when a test fails. So in this case, at the end of the ``__init__`` method, the contents list looks like ``[None]`` -- a list with a single None object in it. No wonder it failed later when we tried to write that ``None`` to a file! -But why? Well, looking at the ``__init__``, it looks like content gets set to None by default:: +But why? Well, looking at the ``__init__``, it looks like content gets set to None by default: .. code-block:: python @@ -561,13 +561,17 @@ OK, did you do something as simple as this? That's it! But what does that mean? This line: -``class Body(Element):`` +.. code-block:: python + + class Body(Element): -means: make a new subclass of the ``Element`` tag called "Body". +means: make a new subclass of the ``Element`` tag called ``Body``. and this line: -`` tag = 'body'`` +.. code-block:: python + + tag = 'body' means: set the ``tag`` class attribute to ``'body'``. Since this class attribute was set on the ``Element`` class already, this is called "overriding" the tag attribute. @@ -585,7 +589,7 @@ Let's run the tests and see if this worked:: =========================== 7 passed in 0.02 seconds =========================== -Success!. We now have three different tags. +Success! We now have three different tags. .. note:: Why the ``Html`` element? Doesn't the ``Element`` class already use the "html" tag? @@ -681,7 +685,7 @@ So we need a way to write an element to a file. How might we do that? Inside the Well, elements already know how to render themselves. This is what is meant by a recursive approach. In the ``render`` method, we want to make use of the ``render`` method itself. -Looking at the signature of the render method:: +Looking at the signature of the render method: .. code-block:: python @@ -827,7 +831,7 @@ Now we are getting a little more interesting. This is easy; you know how to do that, yes? -But the training wheels are off -- you are going to need to write your own tests now. So before you create the ``Head`` element class, write a test for it. You should be able to copy and paste one of the previous tests, and just change the name of the class and the tag value. Remember to give yo test a new name, or it will simply replace the previous test. +But the training wheels are off -- you are going to need to write your own tests now. So before you create the ``Head`` element class, write a test for it. You should be able to copy and paste one of the previous tests, and just change the name of the class and the tag value. Remember to give your test a new name, or it will simply replace the previous test. I like to run the tests as soon as I make a new one. If nothing else, I can make sure I have one more test. @@ -1057,7 +1061,6 @@ OK, I've got 11 tests passing now. How about you? Time for the next step. .. _render_tutorial_4: - Step 4. ------- @@ -1170,7 +1173,7 @@ So we need to render the ``<``, then the ``p``, then a bunch of attribute name=v out_file.write("".join(open_tag)) ... -OK, the rest of the tests are still passing for me; I haven't broken anything else. Now to add code to render the attributes. You'll need to write some sort of loop to loop through each attribute, probably looping through the keys and the values:: +OK, the rest of the tests are still passing for me; I haven't broken anything else. Now to add code to render the attributes. You'll need to write some sort of loop to loop through each attribute, probably looping through the keys and the values: .. code-block:: python @@ -1255,7 +1258,13 @@ Step 5: Create a ``SelfClosingTag`` subclass of Element, to render tags like:: -
` tag around the output of any decorated func @p_decorate - def get_fullname(first_name, last_name): - return f"{first_name} {last_name}" + def get_fullname(first_name, last_name): + return f"{first_name} {last_name}" In [124]: get_fullname('Chris', 'Barker') Out[124]: '
Chris Barker
' @@ -532,8 +532,8 @@ Can you make a version that will wrap any other tag -- specified as a parameter .. code-block:: ipython @add_tag('p') - def get_fullname(first_name, last_name): - return f"{first_name} {last_name}" + def get_fullname(first_name, last_name): + return f"{first_name} {last_name}" In [124]: get_fullname('Chris', 'Barker') Out[124]: 'Chris Barker
' @@ -545,21 +545,21 @@ But: .. code-block:: ipython @add_tag('div') - def get_fullname(first_name, last_name): - return f"{first_name} {last_name}" + def get_fullname(first_name, last_name): + return f"{first_name} {last_name}" In [124]: get_fullname('Chris', 'Barker') Out[124]: '- Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text + Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text
') - assert contents.endswith('
') - assert my_stuff in contents - assert more_stuff in contents - - -def test_render_html(): - my_stuff = 'spam, spam, spam' - el_object = HTML(my_stuff) - more_stuff = 'eggs, eggs, eggs' - el_object.append(more_stuff) - contents = render_element(el_object) - assert contents.startswith('') - assert contents.endswith('') - assert my_stuff in contents - assert more_stuff in contents - - -def test_render_non_strings(): - # this is creating a html page with a single body() element in it - # and a string inside that. - el_object = HTML(Body('any string I like')) - - contents = render_element(el_object) - # make sure extra whitespace at beginning or end doesn't mess things up. - contents = contents.strip() - - print(contents) # so we can see what's going on if a test fails - - # so what should the results be? - # the html tag is the outer tag, so the contents should start and end with that. - assert contents.startswith('') - assert contents.endswith('') - - # the body tags had better be there too - assert '' in contents - assert '') < contents.index('') - # the opening tag should come before the content - assert contents.index('') < contents.index('any string') - - -def test_render_non_strings2(): - """ - Testing nested elements and text, in a more complex way - """ - html = HTML() - body = Body() - html.append(body) - p = Para('any string I like') - p.append('even more random text') - body.append(p) - body.append(Para('and this is a different string')) - contents = render_element(html).strip() - - print(contents) # so we can see what's going on if a test fails - - # so what should the results be? - # the html tag is the outer tag, so the contents should start and end with that. - assert contents.startswith('') - assert contents.endswith('') - - # the body tags had better be there too - assert '' in contents - assert ' tags - assert contents.count('') - - # we want the text, too: - assert 'any string I like' in contents - assert 'even more random text' in contents - assert 'and this is a different string' in contents - - # you could, of course, test much more..but hopefully other things are tested, too. - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = HTML("some content") - cur_ind = 6 * " " - file_contents = render_element(html, cur_ind=cur_ind) - - print(file_contents) - lines = file_contents.split("\n") - - assert lines[0].startswith(cur_ind + "<") - assert lines[1].startswith(cur_ind + Element.indent + "som") - assert lines[-1].startswith(cur_ind + "<") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = HTML("some content") - file_contents = render_element(html, cur_ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented properly - """ - body = Body() - body.append(Para("some text")) - body.append(Para("even more text")) - html = HTML(body) - - file_contents = render_element(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - assert lines[3].startswith(3 * Element.indent + "some") - assert lines[4].startswith(2 * Element.indent + "
") - assert lines[5].startswith(2 * Element.indent + "") - assert lines[6].startswith(3 * Element.indent + "even ") - for i in range(3): - assert lines[-(i + 1)].startswith(i * Element.indent + "<") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_element(t, cur_ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert file_contents.startswith("
- # Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text - #
- p = Para("Here is a paragraph of text", style="text-align: center; font-style: oblique;") - - results = render_element(p) - - assert results.startswith('') - - print(results) - -def test_multiple_attributes(): - #
- # Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text - #
- p = Para("Here is a paragraph of text", - id="fred", - color="red", - size="12px", - ) - - results = render_element(p) - print(results) - - lines = results.split('\n') - assert lines[0].startswith('') - assert 'id="fred"' in lines[0] - assert 'color="red"' in lines[0] - assert 'size="12px"' in lines[0] - -def test_multiple_attributes_title(): - t = Title("Here is a paragraph of text", - id="fred", - color="red", - size="12px", - ) - - results = render_element(t) - print(results) - - lines = results.split('\n') - assert lines[0].startswith('
') - assert 'id="fred"' in lines[0] - assert 'class="special"' in lines[0] - assert 'size="12px"' in lines[0] - - - - diff --git a/source/solutions/html_render/step_1/html_render.py b/source/solutions/html_render/step_1/html_render.py deleted file mode 100644 index d5e6819a..00000000 --- a/source/solutions/html_render/step_1/html_render.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -""" -step 1 of Chris's solution -""" - - -class Element: - - tag = 'html' - - def __init__(self, content=None): - self.content = [] - if content: - self.content.append(content) - - def append(self, content): - self.content.append(content) - - def render(self, out_file, ind=""): - out_file.write("<{}>\n".format(self.tag)) - for stuff in self.content: - out_file.write(stuff + "\n") - out_file.write("{}>\n".format(self.tag)) diff --git a/source/solutions/html_render/step_1/test_html_render.py b/source/solutions/html_render/step_1/test_html_render.py deleted file mode 100644 index 45e51019..00000000 --- a/source/solutions/html_render/step_1/test_html_render.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -test code for html_render.py - -only step 1 -""" - -import io - -from html_render import Element - -# utility function for testing render methods -# needs to be used in multiple tests, so write it once here. - - -def render_result(element, ind=""): - """ - calls element's render method, and returns what got rendered as a string - """ - outfile = io.StringIO() - element.render(outfile, ind) - return outfile.getvalue() - - -def test_init(): - """ - this only tests that it can be initialized -- but it's a start - """ - e = Element() - - e = Element("this is some text") - - -def test_content(): - # fixme: this tests internals!!!! - e = Element("this is some text") - - assert "this is some text" in e.content - - -def test_append(): - e = Element("this is some text") - - e.append("some more text") - - assert "some more text" in e.content - - -def test_two_instances(): - e = Element("this is some text") - e2 = Element("this is some text") - - e.append("some more text") - - assert "some more text" not in e2.content - - -def test_render(): - e = Element("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") diff --git a/source/solutions/html_render/step_2_complete/html_render.py b/source/solutions/html_render/step_2_complete/html_render.py deleted file mode 100644 index 75d8d842..00000000 --- a/source/solutions/html_render/step_2_complete/html_render.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 - -""" -Chris's solution through step 2 with full indentation -""" - - -class TextWrapper: - """ - A simple wrapper that creates a class with a render method - for just text - - This allows the Element classes to render either Element objects or - plain text - - """ - def __init__(self, text): - self.text = text - - def render(self, file_out, current_ind=""): - file_out.write(current_ind + self.text) - - -class Element: - - tag = "html" - indent = " " - - def __init__(self, content=None): - self.content = [] - if content: - # call the classes append method - # so that it can do anything special it needs to do - self.append(content) - - def append(self, content): - """ - add a new piece of content or another element to this element - """ - # note: this changed the internal represntation of content - # it no longer holds strings -- so a test will fail - # but that test was testing internal API -- - # it's probably better remove it - if hasattr(content, 'render'): - self.content.append(content) - else: - self.content.append(TextWrapper(str(content))) - - def render(self, out_file, ind=""): - out_file.write("{}<{}>\n".format(ind, self.tag)) - for stuff in self.content: - stuff.render(out_file, ind + self.indent) - out_file.write("\n") - out_file.write("{}{}>".format(ind, self.tag)) - - -class Html(Element): - tag = 'html' - - -class Body(Element): - tag = "body" - - -class P(Element): - tag = "p" - diff --git a/source/solutions/html_render/step_2_complete/test_html_render.py b/source/solutions/html_render/step_2_complete/test_html_render.py deleted file mode 100644 index c9346389..00000000 --- a/source/solutions/html_render/step_2_complete/test_html_render.py +++ /dev/null @@ -1,201 +0,0 @@ -""" -test code for html_render.py - -includes through step 2 -""" -import io - -from html_render import Element, Html, Body, P, TextWrapper - - -# utility function for testing render methods -# needs to be used in multiple tests, so write it once here. - - -def render_result(element, ind=""): - """ - calls element's render method, and returns what got rendered as a string - """ - outfile = io.StringIO() - element.render(outfile, ind) - return outfile.getvalue() - - -def test_init(): - """ - this only tests that it can be initialized -- but it's a start - """ - e = Element() - - e = Element("this is some text") - - -# These two tests were testing internals -# so they failed when I added the TextWrapper -# but I"m removing them because tests really should be testing -# the external API. -# def test_content(): -# # fixme: this tests internals!!!! -# e = Element("this is some text") - -# assert "this is some text" in e.content - -# def test_append(): -# e = Element("this is some text") - -# e.append("some more text") - -# assert "some more text" in e.content - - -def test_two_instances(): - e = Element("this is some text") - e2 = Element("this is some text") - - e.append("some more text") - - assert "some more text" not in e2.content - - -def test_render(): - e = Element("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_html(): - e = Html("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_body(): - e = Body("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("
") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - - assert lines[3].startswith(3 * Element.indent + "some") diff --git a/source/solutions/html_render/step_2_noindent/html_render.py b/source/solutions/html_render/step_2_noindent/html_render.py deleted file mode 100644 index 9dbd2110..00000000 --- a/source/solutions/html_render/step_2_noindent/html_render.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 - -""" -Chris's solution through step 2 without indentation -""" - - -class TextWrapper: - """ - A simple wrapper that creates a class with a render method - for just text - - This allows the Element classes to render either Element objects or - plain text - - """ - def __init__(self, text): - self.text = text - - def render(self, file_out, current_ind=""): - file_out.write(current_ind + self.text) - - -class Element: - - tag = 'html' - - def __init__(self, content=None): - self.content = [] - if content: - # call the classes append method - # so that it can do anything special it needs to do - self.append(content) - - def append(self, content): - """ - add a new piece of content or another element to this element - """ - # note: this changed the internal represntation of content - # it no longer holds strings -- so a test will fail - # but that test was testing internal API -- - # it's probably better remove it - if hasattr(content, 'render'): - self.content.append(content) - else: - self.content.append(TextWrapper(str(content))) - - def render(self, out_file, ind=""): - out_file.write("<{}>\n".format(self.tag)) - for stuff in self.content: - stuff.render(out_file) - out_file.write("\n") - out_file.write("{}>".format(self.tag)) - - -class Html(Element): - tag = 'html' - - -class Body(Element): - tag = "body" - - -class P(Element): - tag = "p" - diff --git a/source/solutions/html_render/step_2_noindent/test_html_render.py b/source/solutions/html_render/step_2_noindent/test_html_render.py deleted file mode 100644 index 7fed7dc3..00000000 --- a/source/solutions/html_render/step_2_noindent/test_html_render.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -test code for html_render.py - -includes through step 2 without indentation -""" -import io - -from html_render import Element, Html, Body, P, TextWrapper - - -# utility function for testing render methods -# needs to be used in multiple tests, so write it once here. - - -def render_result(element, ind=""): - """ - calls element's render method, and returns what got rendered as a string - """ - outfile = io.StringIO() - element.render(outfile, ind) - return outfile.getvalue() - - -def test_init(): - """ - this only tests that it can be initialized -- but it's a start - """ - e = Element() - - e = Element("this is some text") - - -# These two tests were testing internals -# so they failed when I added the TextWrapper -# but I"m removing them because tests really should be testing -# the external API. -# def test_content(): -# # fixme: this tests internals!!!! -# e = Element("this is some text") - -# assert "this is some text" in e.content - -# def test_append(): -# e = Element("this is some text") - -# e.append("some more text") - -# assert "some more text" in e.content - - -def test_two_instances(): - e = Element("this is some text") - e2 = Element("this is some text") - - e.append("some more text") - - assert "some more text" not in e2.content - - -def test_render(): - e = Element("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_html(): - e = Html("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_body(): - e = Body("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_p(): - e = P("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - assert False - diff --git a/source/solutions/html_render/step_3/html_render.py b/source/solutions/html_render/step_3/html_render.py deleted file mode 100644 index 8cfdce28..00000000 --- a/source/solutions/html_render/step_3/html_render.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 - -""" -Chris's solution through step 3 -""" - - -class TextWrapper: - """ - A simple wrapper that creates a class with a render method - for just text - - This allows the Element classes to render either Element objects or - plain text - - """ - def __init__(self, text): - self.text = text - - def render(self, file_out, current_ind=""): - file_out.write(current_ind + self.text) - - -class Element: - - tag = "html" - indent = " " - - def __init__(self, content=None): - self.content = [] - if content: - # call the classes append method - # so that it can do anything special it needs to do - self.append(content) - - def append(self, content): - """ - add a new piece of content or another element to this element - """ - # note: this changed the internal represntation of content - # it no longer holds strings -- so a test will fail - # but that test was testing internal API -- - # it's probably better remove it - if hasattr(content, 'render'): - self.content.append(content) - else: - self.content.append(TextWrapper(str(content))) - - def render(self, out_file, ind=""): - out_file.write("{}<{}>\n".format(ind, self.tag)) - for stuff in self.content: - stuff.render(out_file, ind + self.indent) - out_file.write("\n") - out_file.write("{}{}>".format(ind, self.tag)) - - -class OneLineTag(Element): - def render(self, out_file, ind=""): - # there is some repition here -- maybe factor that out? - out_file.write("{}<{}>".format(ind, self.tag)) - for stuff in self.content: - stuff.render(out_file) - out_file.write("{}>".format(self.tag)) - - -class Html(Element): - tag = 'html' - - -class Body(Element): - tag = "body" - - -class P(Element): - tag = "p" - - -class Head(Element): - tag = "head" - - -class Title(OneLineTag): - tag = "title" - - diff --git a/source/solutions/html_render/step_3/test_html_render.py b/source/solutions/html_render/step_3/test_html_render.py deleted file mode 100644 index 6f183363..00000000 --- a/source/solutions/html_render/step_3/test_html_render.py +++ /dev/null @@ -1,293 +0,0 @@ -""" -test code for html_render.py - -includes through step 3 -""" - -import io - -from html_render import (Element, - Html, - Body, - P, - TextWrapper, - Head, - Title, - ) - -# utility function for testing render methods -# needs to be used in multiple tests, so write it once here. - - -def render_result(element, ind=""): - """ - calls element's render method, and returns what got rendered as a string - """ - outfile = io.StringIO() - element.render(outfile, ind) - return outfile.getvalue() - - -def test_init(): - """ - this only tests that it can be initialized -- but it's a start - """ - e = Element() - - e = Element("this is some text") - - -# These two tests were testing internals -# so they failed when I added the TextWrapper -# but I"m removing them because tests really should be testing -# the external API. -# def test_content(): -# # fixme: this tests internals!!!! -# e = Element("this is some text") - -# assert "this is some text" in e.content - -# def test_append(): -# e = Element("this is some text") - -# e.append("some more text") - -# assert "some more text" in e.content - - -def test_two_instances(): - e = Element("this is some text") - e2 = Element("this is some text") - - e.append("some more text") - - assert "some more text" not in e2.content - - -def test_render(): - e = Element("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_html(): - e = Html("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_body(): - e = Body("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("") - - -def test_p(): - e = P("this is some text") - e.append("and this is some more text") - - file_contents = render_result(e) - - assert("this is some text") in file_contents - assert("and this is some more text") in file_contents - - assert file_contents.startswith("") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_non_str(): - """ you should be able to pass anything in, and it will get - "stringified" - """ - e = P(34) # a number - e.append((3, 4, 5)) # even a tuple - - file_contents = render_result(e) - - print(file_contents) - assert("34") in file_contents - assert("(3, 4, 5)") in file_contents - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - # assert False - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - body.append(P("even more text")) - - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - assert lines[3].startswith(3 * Element.indent + "some") - assert lines[4].startswith(2 * Element.indent + "") - assert lines[5].startswith(2 * Element.indent + "") - assert lines[6].startswith(3 * Element.indent + "even ") - for i in range(3): - assert lines[-(i + 1)].startswith(i * Element.indent + "<") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_result(t, ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert "\n" not in file_contents - assert file_contents.startswith("
") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_non_str(): - """ you should be able to pass anything in, and it will get - "stringified" - """ - e = P(34) # a number - e.append((3, 4, 5)) # even a tuple - - file_contents = render_result(e) - - print(file_contents) - assert("34") in file_contents - assert("(3, 4, 5)") in file_contents - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - # assert False - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - - assert lines[3].startswith(3 * Element.indent + "some") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_result(t, ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert "\n" not in file_contents - assert "> " not in file_contents - assert file_contents.startswith("") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_non_str(): - """ you should be able to pass anything in, and it will get - "stringified" - """ - e = P(34) # a number - e.append((3, 4, 5)) # even a tuple - - file_contents = render_result(e) - - print(file_contents) - assert("34") in file_contents - assert("(3, 4, 5)") in file_contents - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - # assert False - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - - assert lines[3].startswith(3 * Element.indent + "some") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_result(t, ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert "\n" not in file_contents - assert "> " not in file_contents - assert file_contents.startswith("") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_non_str(): - """ you should be able to pass anything in, and it will get - "stringified" - """ - e = P(34) # a number - e.append((3, 4, 5)) # even a tuple - - file_contents = render_result(e) - - print(file_contents) - assert("34") in file_contents - assert("(3, 4, 5)") in file_contents - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - # assert False - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - - assert lines[3].startswith(3 * Element.indent + "some") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_result(t, ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert "\n" not in file_contents - assert "> " not in file_contents - assert file_contents.startswith("") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_non_str(): - """ you should be able to pass anything in, and it will get - "stringified" - """ - e = P(34) # a number - e.append((3, 4, 5)) # even a tuple - - file_contents = render_result(e) - - print(file_contents) - assert("34") in file_contents - assert("(3, 4, 5)") in file_contents - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - this test does not yet include indentation - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - # assert False - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - - assert lines[3].startswith(3 * Element.indent + "some") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_result(t, ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert "\n" not in file_contents - assert "> " not in file_contents - assert file_contents.startswith("") - assert file_contents.strip().endswith("
") - - -def test_text_wrapper(): - tw = TextWrapper("A basic piece of text") - - file_contents = render_result(tw) - assert file_contents == "A basic piece of text" - - -def test_non_str(): - """ you should be able to pass anything in, and it will get - "stringified" - """ - e = P(34) # a number - e.append((3, 4, 5)) # even a tuple - - file_contents = render_result(e) - - print(file_contents) - assert("34") in file_contents - assert("(3, 4, 5)") in file_contents - - -def test_sub_element(): - """ - tests that you can add another element and still render properly - """ - page = Html() - page.append("some plain text.") - page.append(P("A simple paragraph of text")) - page.append("Some more plain text.") - - file_contents = render_result(page) - - # note: the above tests should make sure that the tags are getting rendered. - assert "some plain text" in file_contents - assert "A simple paragraph of text" in file_contents - assert "Some more plain text." in file_contents - assert "some plain text" in file_contents - - -def test_step_2_noindent(): - """ - This is more if an integration test -- a number of things together - - """ - page = Html() - body = Body() - page.append(body) - body.append(P("a small paragraph of text")) - body.append(P("another small paragraph of text")) - body.append(P("and here is a bit more")) - - file_contents = render_result(page).strip() - - print(file_contents) - # teh DOCTYPE tag messed this up :-( - # assert file_contents.startswith("") - assert file_contents.endswith("") - assert "a small paragraph of text" in file_contents - assert "" in file_contents - # you could do more here, but it should all be covered above. - # assert False - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - file_contents = render_result(html, ind=" ") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[0].startswith(" <") - assert lines[-1].startswith(" <") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Element("some content") - file_contents = render_result(html, ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented fully - """ - body = Body() - body.append(P("some text")) - html = Html(body) - - file_contents = render_result(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): # this needed to be adapted to the') - assert contents.endswith('
') - assert my_stuff in contents - assert more_stuff in contents - - -def test_render_html(): - my_stuff = 'spam, spam, spam' - el_object = Html(my_stuff) - more_stuff = 'eggs, eggs, eggs' - el_object.append(more_stuff) - contents = render_element(el_object) - assert contents.startswith('') - assert contents.endswith('') - assert my_stuff in contents - assert more_stuff in contents - - -def test_render_non_strings(): - # this is creating a html page with a single body() element in it - # and a string inside that. - el_object = Html(Body('any string I like')) - - contents = render_element(el_object) - # make sure extra whitespace at beginning or end doesn't mess things up. - contents = contents.strip() - - print(contents) # so we can see what's going on if a test fails - - # so what should the results be? - # the html tag is the outer tag, so the contents should start and end with that. - assert contents.startswith('') - assert contents.endswith('') - - # the body tags had better be there too - assert '' in contents - assert '') < contents.index('') - # the opening tag should come before the content - assert contents.index('') < contents.index('any string') - - -def test_render_non_strings2(): - """ - Testing nested elements and text, in a more complex way - """ - html = Html() - body = Body() - html.append(body) - p = P('any string I like') - p.append('even more random text') - body.append(p) - body.append(P('and this is a different string')) - contents = render_element(html).strip() - - print(contents) # so we can see what's going on if a test fails - - # so what should the results be? - # the html tag is the outer tag, so the contents should start and end with that. - assert contents.startswith('') - assert contents.endswith('') - - # the body tags had better be there too - assert '' in contents - assert ' tags - assert contents.count('') - - # we want the text, too: - assert 'any string I like' in contents - assert 'even more random text' in contents - assert 'and this is a different string' in contents - - # you could, of course, test much more..but hopefully other things are tested, too. - - -def test_indent(): - """ - Tests that the indentation gets passed through to the renderer - """ - html = Html("some content") - cur_ind = 6 * " " - file_contents = render_element(html, cur_ind=cur_ind) - - print(file_contents) - lines = file_contents.split("\n") - - assert lines[0].startswith(cur_ind + "<") - assert lines[1].startswith(cur_ind + Element.indent + "som") - assert lines[-1].startswith(cur_ind + "<") - - -def test_indent_contents(): - """ - The contents in a element should be indented more than the tag - by the amount in the indent class attribute - """ - html = Html("some content") - file_contents = render_element(html, cur_ind="") - - print(file_contents) - lines = file_contents.split("\n") - assert lines[1].startswith(Element.indent) - - -def test_multiple_indent(): - """ - make sure multiple levels get indented properly - """ - body = Body() - body.append(P("some text")) - body.append(P("even more text")) - html = Html(body) - - file_contents = render_element(html) - - print(file_contents) - lines = file_contents.split("\n") - for i in range(3): - assert lines[i].startswith(i * Element.indent + "<") - assert lines[3].startswith(3 * Element.indent + "some") - assert lines[4].startswith(2 * Element.indent + "
") - assert lines[5].startswith(2 * Element.indent + "") - assert lines[6].startswith(3 * Element.indent + "even ") - for i in range(3): - assert lines[-(i + 1)].startswith(i * Element.indent + "<") - - -def test_title(): - """ - This will implicitly test the OneLineTag element - """ - t = Title("Isn't this a nice title?") - - # making sure indentation still works - file_contents = render_element(t, cur_ind=" ") - - print(file_contents) - # no "strip()" -- making sure there are no extra newlines - assert file_contents.startswith("
- # Here is a Pgraph of text -- there could be more of them, but this is enough to show that we can do some text - #
- p = P("Here is a paragraph of text", style="text-align: center; font-style: oblique;") - - results = render_element(p) - - assert results.startswith('') - - print(results) - -def test_multiple_attributes(): - #
- # Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text - #
- p = P("Here is a paragraph of text", - id="fred", - color="red", - size="12px", - ) - - results = render_element(p) - print(results) - - lines = results.split('\n') - assert lines[0].startswith('') - assert 'id="fred"' in lines[0] - assert 'color="red"' in lines[0] - assert 'size="12px"' in lines[0] - -def test_multiple_attributes_title(): - t = Title("Here is a paragraph of text", - id="fred", - color="red", - size="12px", - ) - - results = render_element(t) - print(results) - - lines = results.split('\n') - assert lines[0].startswith('
')
-    assert 'id="fred"' in lines[0]
-    assert 'class="special"' in lines[0]
-    assert 'size="12px"' in lines[0]
-
-
-
-
diff --git a/source/solutions/mailroom/mailroom_basic/mailroom.py b/source/solutions/mailroom/mailroom_basic/mailroom.py
deleted file mode 100755
index 9fd134a0..00000000
--- a/source/solutions/mailroom/mailroom_basic/mailroom.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#!/usr/bin/env python3
-
-"""
-Mailroom Exercise -- as of Session 3 -- no dictionaries or Exceptions
-"""
-
-from textwrap import dedent  # nifty utility!
-import math
-
-# In memory representation of the donor database
-# using a tuple for each donor
-# -- kind of like a record in a database table
-# the donations are in a list -- so you can add to them
-# Note the mutable inside an immutable
-
-donor_db = [("William Gates, III", [653772.32, 12.17]),
-            ("Jeff Bezos", [877.33]),
-            ("Paul Allen", [663.23, 43.87, 1.32]),
-            ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]),
-            ]
-
-#loop through the donor list and print the 0th element of the list
-def print_donors():
-    print("Donor list:\n")
-    for donor in donor_db:
-        print(donor[0])
-
-
-def find_donor(name):
-    """
-    find a donor in the donor db
-
-    :param: the name of the donor
-
-    :returns: The donor data structure -- None if not in the donor_db
-    """
-    for donor in donor_db:
-        # do a case-insenstive compare
-        if name.strip().lower() == donor[0].lower():
-            return donor
-    return None
-
-
-def main_menu_selection():
-    """
-    Print out the main application menu and then read the user input.
-    """
-    action = input(dedent('''
-      Choose an action:
-
-      't' - Send a Thank You
-      'r' - Create a Report
-      'q' - Quit
-
-      > '''))
-    return action.strip()
-
-
-def gen_letter(donor):
-    """
-    Generate a thank you letter for the donor
-
-    :param: donor tuple
-
-    :returns: string with letter
-    """
-    return dedent('''
-          Dear {}
-
-          Thank you for your very kind donation of ${:.2f}.
-          It will be put to very good use.
-
-                         Sincerely,
-                            -The Team
-          '''.format(donor[0], donor[1][-1]))
-
-
-def send_thank_you():
-    """
-    Execute the logic to record a donation and generate a thank you message.
-    """
-    # Read a valid donor to send a thank you from, handling special commands to
-    # let the user navigate as defined.
-    while True:
-        name = input("Enter a donor's name "
-                     "(or 'list' to see all donors or 'menu' to exit)> ").strip()
-        if name == "list":
-            print_donors()
-        elif name == "menu":
-            return
-        else:
-            break
-
-    # Now prompt the user for a donation amount to apply. Since this is
-    # also an exit point to the main menu, we want to make sure this is
-    # done before mutating the donors list object.
-    while True:
-        amount_str = input("Enter a donation amount (or 'menu' to exit) > ").strip()
-        if amount_str == "menu":
-            return
-        # Make sure amount is a valid amount before leaving the input loop
-        amount = float(amount_str)
-        # NOTE: this is getting a bit carried away...
-        if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00:
-            print("error: donation amount is invalid\n")
-            continue  # not really needed, but makes it more clear
-        else:
-            break
-
-    # If this is a new user, ensure that the database has the necessary data structure.
-    donor = find_donor(name)
-    if donor is None:
-        donor = (name, [])
-        donor_db.append(donor)
-
-    # Record the donation
-    # Note how the donor object can be manipulated while it is in the donors list.
-    donor[1].append(amount)
-
-    print(gen_letter(donor))
-
-
-def sort_key(item):
-    return item[1]
-
-
-def print_donor_report():
-    """
-    Generate the report of the donors and amounts donated.
-    """
-    # First, reduce the raw data into a summary list view
-    report_rows = []
-    for (name, gifts) in donor_db:
-        total_gifts = sum(gifts)
-        num_gifts = len(gifts)
-        avg_gift = total_gifts / num_gifts
-        report_rows.append((name, total_gifts, num_gifts, avg_gift))
-
-    # sort the report data
-    report_rows.sort(key=sort_key)
-    # print it out in with a nice format.
-    print("{:25s} | {:11s} | {:9s} | {:12s}".format(
-          "Donor Name", "Total Given", "Num Gifts", "Average Gift"))
-    print("-" * 66)
-    for row in report_rows:
-        print("{:25s}   {:11.2f}   {:9d}   {:12.2f}".format(*row))
-
-if __name__ == "__main__":
-    running = True
-    while running:
-        selection = main_menu_selection()
-        if selection == "t":
-            send_thank_you()
-        elif selection == "r":
-            print_donor_report()
-        elif selection == "q":
-            running = False
-        else:
-            print("error: menu selection is invalid!")
diff --git a/source/solutions/mailroom/mailroom_test/mailroom2.py b/source/solutions/mailroom/mailroom_test/mailroom2.py
deleted file mode 100755
index 49dfd81d..00000000
--- a/source/solutions/mailroom/mailroom_test/mailroom2.py
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python
-"""
-mailroom assignment
-
-This version uses a dict for the main db, and exception handling to
-check input, and has been factored to be amenable to testing.
-"""
-
-import sys
-import math
-
-# handy utility to make pretty printing easier
-from textwrap import dedent
-
-
-# In memory representation of the donor database
-# using a tuple for each donor
-# -- kind of like a record in a database table
-# using a dict with a lower case version of the donor's name as the key
-# This makes it easier to have a 'normalized' key.
-#  you could get a bit fancier by having each "record" be a dict, with
-#   "name" and "donations" as keys.
-def get_donor_db():
-    return {'william gates iii': ("William Gates III", [653772.32, 12.17]),
-            'jeff bezos': ("Jeff Bezos", [877.33]),
-            'paul allen': ("Paul Allen", [663.23, 43.87, 1.32]),
-            'mark zuckerberg': ("Mark Zuckerberg", [1663.23, 4300.87, 10432.0]),
-            }
-
-
-def list_donors():
-    """
-    creates a list of the donors as a string, so they can be printed
-
-    Not calling print from here makes it more flexible and easier to
-    test
-    """
-    listing = ["Donor list:"]
-    for donor in donor_db.values():
-        listing.append(donor[0])
-    return "\n".join(listing)
-
-
-def find_donor(name):
-    """
-    find a donor in the donor db
-
-    :param: the name of the donor
-
-    :returns: The donor data structure -- None if not in the donor_db
-    """
-    key = name.strip().lower()
-    return donor_db.get(key)
-
-
-def add_donor(name):
-    """
-    Add a new donor to the donor db
-
-    :param: the name of the donor
-
-    :returns: the new Donor data structure
-    """
-    name = name.strip()
-    donor = (name, [])
-    donor_db[name.lower()] = donor
-    return donor
-
-
-def main_menu_selection():
-    """
-    Print out the main application menu and then read the user input.
-    """
-    action = input(dedent('''
-      Choose an action:
-
-      1 - Send a Thank You
-      2 - Create a Report
-      3 - Send letters to everyone
-      4 - Quit
-
-      > '''))
-    return action.strip()
-
-
-def gen_letter(donor):
-    """
-    Generate a thank you letter for the donor
-
-    :param: donor tuple
-
-    :returns: string with letter
-
-    note: This doesn't actually write to a file -- that's a separate
-          function. This makes it more flexible and easier to test.
-    """
-    return dedent('''Dear {0:s},
-
-          Thank you for your very kind donation of ${1:.2f}.
-          It will be put to very good use.
-
-                         Sincerely,
-                            -The Team
-          '''.format(donor[0], donor[1][-1]))
-
-
-def send_thank_you():
-    """
-    Execute the logic to record a donation and generate a thank you message.
-    """
-    # Read a valid donor to send a thank you from, handling special commands to
-    # let the user navigate as defined.
-    while True:
-        name = input("Enter a donor's name (or list to see all donors or 'menu' to exit)> ").strip()
-        if name == "list":
-            print(list_donors())
-        elif name == "menu":
-            return
-        else:
-            break
-
-    # Now prompt the user for a donation amount to apply. Since this is
-    # also an exit point to the main menu, we want to make sure this is
-    # done before mutating the db.
-    while True:
-        amount_str = input("Enter a donation amount (or 'menu' to exit)> ").strip()
-        if amount_str == "menu":
-            return
-        # Make sure amount is a valid amount before leaving the input loop
-        try:
-            amount = float(amount_str)
-            # extra check here -- unlikely that someone will type "NaN", but
-            # it IS possible, and it is a valid floating point number:
-            # http://en.wikipedia.org/wiki/NaN
-            if math.isnan(amount) or math.isinf(amount) or round(amount, 2) == 0.00:
-                raise ValueError
-        # in this case, the ValueError could be raised by the float() call, or by the NaN-check
-        except ValueError:
-            print("error: donation amount is invalid\n")
-        else:
-            break
-
-    # If this is a new user, ensure that the database has the necessary
-    # data structure.
-    donor = find_donor(name)
-    if donor is None:
-        donor = add_donor(name)
-
-    # Record the donation
-    donor[1].append(amount)
-    print(gen_letter(donor))
-
-
-def sort_key(item):
-    # used to sort on name in donor_db
-    return item[1]
-
-
-def generate_donor_report():
-    """
-    Generate the report of the donors and amounts donated.
-
-    :returns: the donor report as a string.
-    """
-    # First, reduce the raw data into a summary list view
-    report_rows = []
-    for (name, gifts) in donor_db.values():
-        total_gifts = sum(gifts)
-        num_gifts = len(gifts)
-        avg_gift = total_gifts / num_gifts
-        report_rows.append((name, total_gifts, num_gifts, avg_gift))
-
-    # sort the report data
-    report_rows.sort(key=sort_key)
-    report = []
-    report.append("{:25s} | {:11s} | {:9s} | {:12s}".format("Donor Name",
-                                                            "Total Given",
-                                                            "Num Gifts",
-                                                            "Average Gift"))
-    report.append("-" * 66)
-    for row in report_rows:
-        report.append("{:25s}   ${:10.2f}   {:9d}   ${:11.2f}".format(*row))
-    return "\n".join(report)
-
-
-def save_letters_to_disk():
-    """
-    make a letter for each donor, and save it to disk.
-    """
-    for donor in donor_db.values():
-        letter = gen_letter(donor)
-        # I don't like spaces in filenames...
-        filename = donor[0].replace(" ", "_") + ".txt"
-        open(filename, 'w').write(letter)
-
-
-def print_donor_report():
-    print(generate_donor_report())
-
-
-def quit():
-    sys.exit(0)
-
-if __name__ == "__main__":
-
-    donor_db = get_donor_db()
-
-    running = True
-
-    selection_dict = {"1": send_thank_you,
-                      "2": print_donor_report,
-                      "3": save_letters_to_disk,
-                      "4": quit}
-
-    while True:
-        selection = main_menu_selection()
-        try:
-            selection_dict[selection]()
-        except KeyError:
-            print("error: menu selection is invalid!")
diff --git a/source/solutions/mailroom/mailroom_test/test_mailroom2.py b/source/solutions/mailroom/mailroom_test/test_mailroom2.py
deleted file mode 100644
index e633e575..00000000
--- a/source/solutions/mailroom/mailroom_test/test_mailroom2.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python
-
-"""
-unit tests for the mailroom program
-"""
-import os
-
-import mailroom2 as mailroom
-
-# so that it's there for the tests
-mailroom.donor_db = mailroom.get_donor_db()
-
-
-def test_list_donors():
-    listing = mailroom.list_donors()
-
-    # hard to test this throughly -- better not to hard code the entire
-    # thing. But check for a few aspects -- this will catch the likely
-    # errors
-    assert listing.startswith("Donor list:\n")
-    assert "Jeff Bezos" in listing
-    assert "William Gates III" in listing
-    assert len(listing.split('\n')) == 5
-
-
-def test_find_donor():
-    """ checks a donor that is there, but with odd case and spaces"""
-    donor = mailroom.find_donor("jefF beZos ")
-
-    assert donor[0] == "Jeff Bezos"
-
-
-def test_find_donor_not():
-    "test one that's not there"
-    donor = mailroom.find_donor("Jeff Bzos")
-
-    assert donor is None
-
-
-def test_gen_letter():
-    """ test the donor letter """
-
-    # create a sample donor
-    donor = ("Fred Flintstone", [432.45, 65.45, 230.0])
-    letter = mailroom.gen_letter(donor)
-    # what to test? tricky!
-    assert letter.startswith("Dear Fred Flintstone")
-    assert letter.endswith("-The Team\n")
-    assert "donation of $230.00" in letter
-
-
-def test_add_donor():
-    name = "Fred Flintstone  "
-
-    donor = mailroom.add_donor(name)
-    donor[1].append(300)
-    assert donor[0] == "Fred Flintstone"
-    assert donor[1] == [300]
-    assert mailroom.find_donor(name) == donor
-
-
-def test_generate_donor_report():
-
-    report = mailroom.generate_donor_report()
-
-    print(report)  # printing so you can see it if it fails
-    # this is pretty tough to test
-    # these are not great, because they will fail if unimportant parts of the
-    # report are changed.
-    # but at least you know that codes working now.
-    assert report.startswith("Donor Name                | Total Given | Num Gifts | Average Gift")
-
-    assert "Jeff Bezos                  $    877.33           1   $     877.33" in report
-
-
-def test_save_letters_to_disk():
-    """
-    This only tests that the files get created, but that's a start
-
-    Note that the contents of the letter was already
-    tested with test_gen_letter
-    """
-
-    mailroom.save_letters_to_disk()
-
-    assert os.path.isfile('Jeff_Bezos.txt')
-    assert os.path.isfile('William_Gates_III.txt')
-    # check that it'snot empty:
-    with open('William_Gates_III.txt') as f:
-        size = len(f.read())
-    assert size > 0
-
-
-if __name__ == "__main__":
-    # this is best run with a test runner, like pytest
-    # But if not, at least this will run them all.
-    test_list_donors()
-    test_find_donor()
-    test_find_donor_not()
-    test_gen_letter()
-    test_add_donor()
-    test_generate_donor_report()
-    test_save_letters_to_disk()
-    print("All tests Passed")
diff --git a/source/solutions/sparse_array/slice_sparse.py b/source/solutions/sparse_array/slice_sparse.py
deleted file mode 100644
index c40fb76a..00000000
--- a/source/solutions/sparse_array/slice_sparse.py
+++ /dev/null
@@ -1,139 +0,0 @@
-
-"""
-example of emulating a sequence using slices
-"""
-
-
-class SparseArray(object):
-
-    def __init__(self, my_array=()):
-        self.length = len(my_array)
-        self.sparse_array = self._convert_to_sparse(my_array)
-
-    def _convert_to_sparse(self, my_array):
-        sparse_array = {}
-        for index, number in enumerate(my_array):
-            if number:
-                sparse_array[index] = number
-        return sparse_array
-
-    def __len__(self):
-        return self.length
-
-    def __str__(self):
-        msg = ['SparseArray: [']
-        for i in range(self.length):
-            msg.append("{} ".format(self[i]))
-        msg.append(']')
-        return "".join(msg)
-
-    def __getitem__(self, index):
-        # this version supports slicing -- far more complicated
-        mini_array = []
-        if isinstance(index, slice):
-            start, stop, step = index.indices(len(self))
-            if step is None:
-                step = 1
-            key = start
-            mini_array = []
-            while key < stop:
-                mini_array.append(self[key])
-                key += step
-            return mini_array
-        else:
-            # makes it an int, even if it's some other
-            # type that supports being used as an index
-            index = index.__index__()
-            return self._get_single_value(index)
-
-    def _get_single_value(self, key):
-        if key >= self.length:
-            raise IndexError('array index out of range')
-        else:
-            return self.sparse_array.get(key, 0)
-
-    def __setitem__(self, index, value):
-        if isinstance(index, slice):
-            start, stop, step = index.indices(len(self))
-            if step is None:
-                step = 1
-            key = start
-            new_values = []
-            new_keys = []
-            for each in value:
-                if key < stop:
-                    self[key] = each
-                else:
-                    # now instead of replacing values, we need to add (a) value(s) in the center,
-                    # and move stuff over, probably want to collect all of the changes,
-                    # and then make a new dictionary
-                    new_values.append(each)
-                    new_keys.append(key)
-                key += step
-            if new_keys:
-                self._add_in_slice(new_keys, new_values)
-        else:
-            index = index.__index__()
-            self._set_single_value(index, value)
-
-    def _set_single_value(self, key, value):
-        if key > self.length:
-            raise IndexError('array assignment index out of range')
-        if value != 0:
-            self.sparse_array[key] = value
-        else:
-            # if the value is being set to zero, we probably need to
-            # remove a key from our dictionary.
-            self.sparse_array.pop(key, None)
-
-    def _add_in_slice(self, new_keys, new_values):
-        # sometimes we need to add in extra values
-        # any existing values
-        # greater than the last key of the new data
-        # will be increased by how many
-        new_dict = {}
-        slice_length = len(new_keys)
-        for k, v in self.sparse_array.items():
-            if k >= new_keys[-1]:
-                # print('change keys')
-                # if greater than slice, change key
-                new_dict[k + slice_length] = v
-            elif k in new_keys:
-                # if this is a key we are changing, change it,
-                # unless we are changing to a zero...
-                new_value = values[new_keys.index(k)]
-                if new_value != 0:
-                    new_dict[k] = new_value
-            else:
-                new_dict[k] = v
-        # what if our new key was not previously in the dictionary?
-        # stick it in now
-        for k in new_keys:
-            if k not in new_dict.keys():
-                new_dict[k] = new_values[new_keys.index(k)]
-        # note we don't want to do update, since we need to make sure we are
-        # getting rid of the old keys, when we moved the value to a new key
-        self.sparse_array = new_dict
-        # now we need to increase the length by the amount we increased our array by
-        self.length += slice_length
-
-    def __delitem__(self, key):
-        # we probably need to move the keys if we are not deleting the last
-        # number, use pop in case it was a zero
-        if key == self.length - 1:
-            self.sparse_array.pop(key, None)
-        else:
-            # since we need to adjust all of the keys after the one we are
-            # deleting, probably most efficient to create a new dictionary
-            new_dict = {}
-            for k, v in self.sparse_array.items():
-                if k >= key:
-                    new_dict[k - 1] = v
-                else:
-                    new_dict[k] = v
-            # note we don't want to do update, since we need to make sure we are
-            # getting rid of the old keys, when we moved the value to a new key
-            self.sparse_array = new_dict
-        # length is now one shorter
-        self.length -= 1
-
diff --git a/source/solutions/sparse_array/sparse_array.py b/source/solutions/sparse_array/sparse_array.py
deleted file mode 100644
index d9543cab..00000000
--- a/source/solutions/sparse_array/sparse_array.py
+++ /dev/null
@@ -1,75 +0,0 @@
-
-"""
-An example of emulating a sequence
-
-A SparseArray is like a list, but only stores the non-zero values
-
-It can be indexed, appended-to, and iterated through.
-
-This version does not support slicing.
-"""
-
-
-class SparseArray(object):
-
-    def __init__(self, my_array=()):
-        """
-        initilize a sparse array
-
-        :param my_array: an initial sequence to start with
-                         if there are zeros in it, they wil not be stored
-        """
-        self.length = len(my_array)
-        # self.sparse_array is a dict that stores only the non-zero items
-        self.sparse_array = self._convert_to_sparse(my_array)
-
-    def _convert_to_sparse(self, my_array):
-        sparse_array = {}
-        for index, number in enumerate(my_array):
-            if number:  # remember that zeros are falsey.
-                sparse_array[index] = number
-        # or the dict comprehension method:
-        # sparse_array = {index:number for index, number in enumerate(my_array) if number}
-        return sparse_array
-
-    def __len__(self):
-        return self.length
-
-    def __getitem__(self, key):
-        # fixme: doesn't handle negative indexes properly
-        try:
-            return self.sparse_array[key]
-        except KeyError:
-            if key >= self.length:
-                raise IndexError('array index out of range')
-            return 0
-
-    def __setitem__(self, key, value):
-        if key > self.length:
-            raise IndexError('array assignment index out of range')
-        if value != 0:
-            self.sparse_array[key] = value
-        else:
-            # if the value is being set to zero, we probably need to
-            # remove a key from our dictionary.
-            self.sparse_array.pop(key, None)
-
-    def __delitem__(self, key):
-        # we probably need to move the keys if we are not deleting the last
-        # number, use pop in case it was a zero
-        if key == self.length - 1:  # it's the last item -- easy.
-            self.sparse_array.pop(key, None)
-        else:
-            # since we need to adjust all of the keys after the one we are
-            # deleting, probably most efficient to create a new dictionary
-            new_dict = {}
-            for k, v in self.sparse_array.items():
-                if k >= key:
-                    new_dict[k - 1] = v
-                else:
-                    new_dict[k] = v
-            # note we don't want to do update, since we need to make sure we are
-            # getting rid of the old keys, when we moved the value to a new key
-            self.sparse_array = new_dict
-        # length is now one shorter
-        self.length -= 1
diff --git a/source/solutions/sparse_array/test_slice_sparse.py b/source/solutions/sparse_array/test_slice_sparse.py
deleted file mode 100644
index 4a7994c8..00000000
--- a/source/solutions/sparse_array/test_slice_sparse.py
+++ /dev/null
@@ -1,129 +0,0 @@
-"""
-this version tests the solution with slicing tests
-"""
-
-
-import pytest
-from slice_sparse import SparseArray
-
-
-def set_up():
-    my_array = [2, 0, 0, 0, 3, 0, 0, 0, 4, 5, 6, 0, 2, 9]
-    my_sparse = SparseArray(my_array)
-    return (my_array, my_sparse)
-
-
-def test_object_exists():
-    my_array, my_sparse = set_up()
-    assert isinstance(my_sparse, SparseArray)
-
-
-def test_get_non_zero_number():
-    my_array, my_sparse = set_up()
-    assert my_sparse[4] == 3
-
-
-def test_get_zero():
-    my_array, my_sparse = set_up()
-    assert my_sparse[1] == 0
-
-
-def test_get_element_not_in_array():
-    my_array, my_sparse = set_up()
-    with pytest.raises(IndexError):
-        my_sparse[14]
-
-
-def test_str():
-    my_array, my_sparse = set_up()
-
-
-def test_get_slice():
-    my_array, my_sparse = set_up()
-    assert my_sparse[2:4] == [0, 0]
-
-
-def test_set_slice():
-    my_array, my_sparse = set_up()
-    my_sparse[2:4] = [2, 3, 4]
-    assert my_sparse[:] == [2, 0, 2, 3, 4, 3, 0, 0, 0, 4, 5, 6, 0, 2, 9]
-
-
-def test_set_slice_over_end():
-    # this slice goes over the end
-    my_array, my_sparse = set_up()
-    print(my_sparse)
-    my_sparse[2:4] = [2, 3, 4]
-    assert my_sparse[:] == [2, 0, 2, 3, 4, 3, 0, 0, 0, 4, 5, 6, 0, 2, 9]
-
-
-def test_get_length():
-    my_array, my_sparse = set_up()
-    assert len(my_sparse) == 14
-
-
-def test_change_number_in_array():
-    my_array, my_sparse = set_up()
-    my_sparse[0] = 3
-    assert my_sparse[0] == 3
-    # make sure others aren't changed
-    assert my_sparse[1] == 0
-    # make sure still same length
-    assert len(my_sparse) == 14
-
-
-def test_change_number_in_array_to_zero():
-    my_array, my_sparse = set_up()
-    my_sparse[4] = 0
-    assert my_sparse[4] == 0
-    # make sure still same length
-    assert len(my_sparse) == 14
-
-
-def test_change_number_in_array_from_zero():
-    my_array, my_sparse = set_up()
-    my_sparse[1] = 4
-    assert my_sparse[1] == 4
-    # make sure still same length
-    assert len(my_sparse) == 14
-
-
-def test_change_slice():
-    my_array, my_sparse = set_up()
-    my_sparse[1:3] = [2, 3]
-    assert my_sparse[1:3] == [2, 3]
-
-
-def test_delete_number():
-    my_array, my_sparse = set_up()
-    del(my_sparse[4])
-    # if we delete the 4 position, should now be zero
-    assert my_sparse[4] == 0
-    # should have smaller length
-    assert len(my_sparse) == 13
-
-
-def test_delete_zero():
-    my_array, my_sparse = set_up()
-    del my_sparse[5]
-    # should still be zero, but should have shorter length
-    assert my_sparse[5] == 0
-    assert len(my_sparse) == 13
-
-
-def test_delete_last_number():
-    my_array, my_sparse = set_up()
-    del(my_sparse[13])
-    # should get an error
-    with pytest.raises(IndexError):
-        my_sparse[13]
-    assert len(my_sparse) == 13
-
-
-def test_indices_change():
-    my_array, my_sparse = set_up()
-    del(my_sparse[3])
-    # next index should have changed
-    # my_sparse[4] was 3 now
-    # my_sparse[3] should be 3
-    assert (my_sparse[3] == 3)
diff --git a/source/solutions/sparse_array/test_sparse_array.py b/source/solutions/sparse_array/test_sparse_array.py
deleted file mode 100644
index 06862292..00000000
--- a/source/solutions/sparse_array/test_sparse_array.py
+++ /dev/null
@@ -1,113 +0,0 @@
-"""
-This tests the solution that does not support slicing
-"""
-
-import pytest
-from sparse_array import SparseArray
-
-
-def set_up():
-    my_array = [2, 0, 0, 0, 3, 0, 0, 0, 4, 5, 6, 0, 2, 9]
-    my_sparse = SparseArray(my_array)
-    return (my_array, my_sparse)
-
-
-def test_object_exists():
-    my_array, my_sparse = set_up()
-    assert isinstance(my_sparse, SparseArray)
-
-
-def test_get_non_zero_number():
-    my_array, my_sparse = set_up()
-    assert my_sparse[4] == 3
-
-
-def test_get_zero():
-    my_array, my_sparse = set_up()
-    assert my_sparse[1] == 0
-
-
-def test_get_element_not_in_array():
-    my_array, my_sparse = set_up()
-    with pytest.raises(IndexError):
-        my_sparse[14]
-
-
-def test_get_length():
-    my_array, my_sparse = set_up()
-    assert len(my_sparse) == 14
-
-
-def test_change_number_in_array():
-    my_array, my_sparse = set_up()
-    my_sparse[0] = 3
-    assert my_sparse[0] == 3
-    # make sure others aren't changed
-    assert my_sparse[1] == 0
-    # make sure still same length
-    assert len(my_sparse) == 14
-
-
-def test_change_number_in_array_to_zero():
-    my_array, my_sparse = set_up()
-    my_sparse[4] = 0
-    assert my_sparse[4] == 0
-    # make sure still same length
-    assert len(my_sparse) == 14
-
-
-def test_change_number_in_array_from_zero():
-    my_array, my_sparse = set_up()
-    my_sparse[1] = 4
-    assert my_sparse[1] == 4
-    # make sure still same length
-    assert len(my_sparse) == 14
-
-
-def test_delete_number():
-    my_array, my_sparse = set_up()
-    del(my_sparse[4])
-    # if we delete the 4 position, should now be zero
-    assert my_sparse[4] == 0
-    # should have smaller length
-    assert len(my_sparse) == 13
-
-
-def test_delete_zero():
-    my_array, my_sparse = set_up()
-    del(my_sparse[5])
-    # should still be zero, but should have shorter length
-    assert my_sparse[5] == 0
-    assert len(my_sparse) == 13
-
-
-def test_delete_last_number():
-    my_array, my_sparse = set_up()
-    del(my_sparse[13])
-    # should get an error?
-    with pytest.raises(IndexError):
-        my_sparse[13]
-    assert len(my_sparse) == 13
-
-
-def test_indices_change():
-    my_array, my_sparse = set_up()
-    del(my_sparse[3])
-    # next index should have changed
-    # my_sparse[4] was 3 now
-    # my_sparse[3] should be 3
-    assert (my_sparse[3] == 3)
-
-
-# this is a way to tell pytest that you expect this test to fail
-@pytest.mark.xfail
-def test_get_slice():
-    my_array, my_sparse = set_up()
-    assert my_sparse[2:4] == [0, 0]
-
-
-@pytest.mark.xfail
-def test_set_slice():
-    my_array, my_sparse = set_up()
-    my_sparse[2:4] = [2, 3, 4]
-    assert my_sparse[:] == [2, 0, 2, 3, 4, 3, 0, 0, 0, 4, 5, 6, 0, 2, 9]
diff --git a/source/supplemental/dev_environment/git_hints.rst b/source/supplemental/dev_environment/git_hints.rst
index 8c19196a..7221e9c2 100644
--- a/source/supplemental/dev_environment/git_hints.rst
+++ b/source/supplemental/dev_environment/git_hints.rst
@@ -248,8 +248,43 @@ There is a saying in the git world:
 
 It's a good way to work -- branching and merging is easy enough it git that it pays off to do it often.
 
+"detached HEAD"
+---------------
 
+Above, we talked about using ``git checkout`` to restore a file to the state it was in in a previous commit, like so::
 
+    git checkout 8e5908a37d7d examples/Session05/maillroom_test.py
+
+But what happens if you do a checkout with a commit, and no specific file?
+
+It does what you might expect -- puts ALL the files back the way they were at that commit. But there is a hitch ... let's see what happens when I do that::
+
+    $ git checkout c03bb5b2c401c
+    Note: checking out 'c03bb5b2c401c'.
+
+    You are in 'detached HEAD' state. You can look around, make experimental
+    changes and commit them, and you can discard any commits you make in this
+    state without impacting any branches by performing another checkout.
+
+    If you want to create a new branch to retain commits you create, you may
+    do so (now or later) by using -b with the checkout command again. Example:
+
+      git checkout -b