diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3b911062..00000000 --- a/.gitignore +++ /dev/null @@ -1,108 +0,0 @@ -.DS_Store -.git-completion.bash - - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ diff --git a/students/FyfyNguyen/session06/mailroom4.py b/students/FyfyNguyen/session06/mailroom4.py new file mode 100644 index 00000000..1b2a0d8e --- /dev/null +++ b/students/FyfyNguyen/session06/mailroom4.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +""" +Mailroom 4 +""" + +import sys +import tempfile +import pathlib +from textwrap import dedent + +donor_db = {"Robyn Rihanna": [100, 200, 300], + "Ariana Grande": [2250, 4000, 1000], + "Beyonce Carter-Knowles": [150000, 3500, 25000], + "Aubrey Drake Graham": [15000, 5500.25, 1200], + "Justin Bieber": [2500, 250, 750.50] + } + +file_dir = pathlib.Path(tempfile.gettempdir()) + + +def print_donors(): + """ + Prints a list of exiting donors in donor_db + """ + for donor in donor_db: + print(donor) + + +def find_donor(name_entered): + """ + Lookup donor in donor_db + + :param: the name of the donor + :returns: the donor data structure -- None if donor not found in donor_db + """ + donor_key = name_entered.strip().title() + return donor_db.get(donor_key) + + +def add_new_donor(name_entered): + """ + Add a new donor to donor_db + + :param: the name of the donor + :returns: the new donor data structure + """ + donor = (name_entered, []) + donor_db[name_entered.title()] = [] + print("Successfully added new donor to database.") + ask_donation(name_entered) + + +def update_donation(name_entered, donation): + donor_db.setdefault(name_entered.title(), []).append(donation) + print("Successfully updated donation amount.") + send_thank_you(name_entered, donation) + + +def ask_donation(name_entered): + """ + Ask the user for donation amount + + :param: the name of the donor + """ + donation = float(input("Enter donation amount >>> ")) + update_donation(name_entered, donation) + + +def confirm_donor(name_entered): + """ + Confirm the user wants to add a new donor to donor_db + + :param: the name of the donor + """ + response = input(f"Donor does not exist. Add {name_entered.title()} to " + "database? [y/n?] >>> ") + if response.strip() == 'y': + add_new_donor(name_entered) + else: + return + + +def send_thank_you(name_entered, donation): + """ + Generate a thank you letter to the donor + + :param: the name of the donor + :param: the donation amount + :returns:string with letter + """ + return dedent("""Dear {}, + + Your generous ${:,.2f} donation just made our day! + + Thank you! + -Our Charity + """.format(name_entered.title(), donation)) + + +def thank_you(): + """ + Execute the logic to record a donation and generate a thank you message + """ + while True: + name_entered = input("Enter the name of a donor or 'list' to view " + "donors >>> ").lower().strip() + if name_entered.strip() == "list": + print_donors() + else: + break + + donor = find_donor(name_entered) + if donor is None: + confirm_donor(name_entered) + else: + ask_donation(name_entered) + + +def thank_you_all(): + """ + Generate a letter for each donor and save it to temporary directory + """ + for donor in donor_db: + letters = send_thank_you(donor, donor_db[donor][-1]) + file_path = file_dir / (donor.replace(" ", "_") + ".txt") + with open(file_path, 'w') as f: + f.write(letters) + print(f'Files downloaded to: {file_dir}') + + +def sort_key(donor): + return donor + + +def create_report(): + """ + Generate report of donors and the amounts donated + + :returns: the donor report as a string + """ + report_rows = [] + for name, donations in donor_db.items(): + total_given = sum(donations) + num_gifts = len(donations) + avg_gifts = total_given / num_gifts + report_rows.append((name, num_gifts, num_gifts, avg_gifts)) + + report_rows.sort(key=sort_key) + report = [] + report.append("{:30s} | {:11s} | {:9s} | {:12s}".format("Donor Name", + "Total Given", + "Num Gifts", + "Average Gift" + )) + report.append("-" * 72) + for row in report_rows: + report.append("{:30s} | {:11d} | {:9d} | ${:12,.2f}".format(*row)) + return "\n".join(report) + + +def print_report(): + print(create_report()) + + +def exit_program(): + """ + Quit the program + """ + print("Exiting Program. Goodbye!") + sys.exit() + + +def main_menu(): + selection = input(dedent(""" + Welcome to Mailroom! + + Select an option: + + 1 - Create a Report + 2 - Send Thank You to a single donor + 3 - Send Thank You to all donors + 4 - Quit + + >>> """)) + return selection.strip() + + +if __name__ == "__main__": + menu_selections = {"1": print_report, + "2": thank_you, + "3": thank_you_all, + "4": exit_program} + + while True: + user_selection = main_menu() + try: + menu_selections[user_selection]() + except KeyError: + print("Error: Invalid Selection. Select from the main menu.") diff --git a/students/FyfyNguyen/session06/test_mailroom4.py b/students/FyfyNguyen/session06/test_mailroom4.py new file mode 100644 index 00000000..74c3e9f5 --- /dev/null +++ b/students/FyfyNguyen/session06/test_mailroom4.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +""" +Mailroom 4 tests +""" + +import pytest +import os +import pathlib +import mailroom4 as mr + + +def test_print_donors(): + listing = mr.print_donors() + assert "Robyn Rihanna" in listing + assert "Aubrey Drake Graham" in listing + + +def test_find_donor(): + donor = mr.find_donor("beyonce caRter Knowles ") + assert donor[0] == "Beyonce Carter Knowles" + + +def test_if_not_find_donor(): + donor = mr.find_donor("Beyonce Carter Knowles") + assert donor is None + + +def test_add_new_donor(): + name = " Jennifer Lopez" + donor = mr.add_new_donor(name) + donor[1].append(750) + assert donor[0] == "Jennifer Lopez" + assert donor[1] == [750] + assert mailroom.find_donor(name) == donor + + +def test_create_report(): + result = mr.create_report() + assert result.startswith("Donor Name | Total Given | Num Gifts | Average Gift") + assert "------------------------------------------------------------------------" in result + assert "Ariana Grande | 3 | 3 | $ 2,416.67" in result + + +def test_thank_you(): + result = mr.thank_you("Chrissy Teigen", 4500) + assert "Dear Chrissy Teigen," in result + assert "$4,500.00" in result + assert "4500" not in result + + +def test_thank_you_all(): + result = mr.thank_you_all("Fyfy Nguyen") + assert result.name == "Fyfy_Nguyen.txt" + + +def test_thank_you_all_files(): + mr.thank_you_all() + for donor in mr.donor_db.items(): + file_path = mr.thank_you_all diff --git a/students/FyfyNguyen/session07/__pycache__/html_render.cpython-37.pyc b/students/FyfyNguyen/session07/__pycache__/html_render.cpython-37.pyc new file mode 100644 index 00000000..58d44c43 Binary files /dev/null and b/students/FyfyNguyen/session07/__pycache__/html_render.cpython-37.pyc differ diff --git a/students/FyfyNguyen/session07/__pycache__/test_html_render.cpython-37-PYTEST.pyc b/students/FyfyNguyen/session07/__pycache__/test_html_render.cpython-37-PYTEST.pyc new file mode 100644 index 00000000..14deac4c Binary files /dev/null and b/students/FyfyNguyen/session07/__pycache__/test_html_render.cpython-37-PYTEST.pyc differ diff --git a/students/FyfyNguyen/session07/html_render.py b/students/FyfyNguyen/session07/html_render.py new file mode 100644 index 00000000..1449b6ab --- /dev/null +++ b/students/FyfyNguyen/session07/html_render.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 + +""" +A class-based system for rendering html. +""" + + +class TextWrapper: + """ + A simple wrapper that creates a class with a render method + for simple text + """ + def __init__(self, text): + self.text = text + + def render(self, file_out, cur_ind=""): + file_out.write(cur_ind + self.text) + + +# This is the framework for the base class +class Element: + tag = "html" + indent = " " + + def __init__(self, content=None, **kwargs): + self.attributes = kwargs + self.contents = [] + if content is not None: + self.append(content) + + def append(self, new_content): + self.contents.append(new_content) + + def _make_tags(self): + attributes = " ".join(['{}="{}"'.format(key, value) for key, value in self.attributes.items()]) + if attributes.strip(): + open_tag = "<{} {}>".format(self.tag, attributes.strip()) + else: + open_tag = "<{}>".format(self.tag) + close_tag = "{}>".format(self._make_tags) + + return open_tag, close_tag + + def render(self, out_file, cur_ind=""): + # loop through the list of contents: + open_tag, close_tag = self._make_tags() + out_file.write(cur_ind + open_tag + "\n") + for content in self.contents: + try: + content.render(out_file, cur_ind + self.indent) + out_file.write("\n") + out_file.write(cur_ind + close_tag) + except AttributeError: + out_file.write(content) + + + # def tag_open(self, out_file): + # outfile.write(f"<{self.tag}{self.attributes_text()}>{self.content_separator}) + + # def tag_close(self, out_file): + # out_file.write(f"{self.tag}>") + + # def tag_content(self, out_file): + # for line in self.comtent: + # line.render(out_file) + # out_file.write(self.content_separator) + + +class Html(Element): + tag = "html" + + def render(self, file_out, cur_ind=""): + file_out.write(cur_ind + "\n") + super().render(file_out, cur_ind=cur_ind) + + +class Body(Element): + tag = "body" + + +class P(Element): + tag = "p" + + +class Head(Element): + tag = "head" + + +class OneLineTag(Element): + def render(self, out_file, cur_ind=""): + open_tag, close_tag = self._make_tags() + out_file.write(cur.ind + open_tag) + for content in self.content: + content.render(out_file) + out_file.write(close_tag) + + +class Title(OneLineTag): + tag = "title" + + +class SelfClosingTag(Element): + def __init__(self, content=None, **kwargs): + if content is not None: + raise TypeError("SelfClosingTag can not contain any content") + super().__init__(self, content=content, **kwargs) + + def append(self, *args, **kwargs): + raise TypeError("SelfClosingTag can not contain any content") + + def render(self, out_file, cur_ind=""): + open_tag, _ = self._make_tags() + out_file.write(ind + open_tag.replace(">", " />")) + # # loop through the list of contents: + # out_file.write(self._open_tag()) + # # out_file.write("\n") + # for content in self.contents: + # try: + # content.render(out_file) + # except AttributeError: + # out_file.write(content) + # out_file.write("\n") + # out_file.write(self._close_tag()) + # out_file.write("\n") + + +class Hr(SelfClosingTag): + tag = "hr" + + +class Br(SelfClosingTag): + tag = "br" + + +class A(OneLineTag): + tag = "a" + + def __init__(self, link, *args, **kwargs): + kwargs['href'] = link + super().__init__(self, *args, **kwargs) + + +class Ul(Element): + tag = "ul" + + +class Li(Element): + tag = "li" + + +class H(OneLineTag): + tag = "H" + + def __init__(self, level, *args, **kwargs): + self.tag = "h" + str(int(level)) + super().__init__(*args, **kwargs) + + +class Meta(SelfClosingTag): + tag = "meta" diff --git a/students/FyfyNguyen/session07/run_html_render.py b/students/FyfyNguyen/session07/run_html_render.py new file mode 100644 index 00000000..9608a65f --- /dev/null +++ b/students/FyfyNguyen/session07/run_html_render.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 + +""" +a simple script can run and test your html rendering classes. + +Uncomment the steps as you add to your rendering. + +""" + +from io import StringIO + +# importing the html_rendering code with a short name for easy typing. +import html_render as hr + + +# writing the file out: +def render_page(page, filename, indent=None): + """ + render the tree of elements + + This uses StringIO to render to memory, then dump to console and + write to file -- very handy! + """ + + f = StringIO() + if indent is None: + page.render(f) + else: + page.render(f, indent) + + print(f.getvalue()) + with open(filename, 'w') as outfile: + outfile.write(f.getvalue()) + + +# Step 1 +######### + +page = hr.Element() + +page.append("Here is a paragraph of text -- there could be more of them, " + "but this is enough to show that we can do some text") + +page.append("And here is another piece of text -- you should be able to add any number") + +render_page(page, "test_html_output1.html") + +# The rest of the steps have been commented out. +# Uncomment them as you move along with the assignment. + +# ## Step 2 +# ########## + +# page = hr.Html() + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text")) + +# body.append(hr.P("And here is another piece of text -- you should be able to add any number")) + +# page.append(body) + +# render_page(page, "test_html_output2.html") + +# # Step 3 +# ########## + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text")) +# body.append(hr.P("And here is another piece of text -- you should be able to add any number")) + +# page.append(body) + +# render_page(page, "test_html_output3.html") + +# # Step 4 +# ########## + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# page.append(body) + +# render_page(page, "test_html_output4.html") + +# # Step 5 +# ######### + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# page.append(body) + +# render_page(page, "test_html_output5.html") + +# # Step 6 +# ######### + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# body.append("And this is a ") +# body.append( hr.A("http://google.com", "link") ) +# body.append("to google") + +# page.append(body) + +# render_page(page, "test_html_output6.html") + +# # Step 7 +# ######### + +# page = hr.Html() + +# head = hr.Head() +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append( hr.H(2, "PythonClass - Class 6 example") ) + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# list = hr.Ul(id="TheList", style="line-height:200%") + +# list.append( hr.Li("The first item in a list") ) +# list.append( hr.Li("This is the second item", style="color: red") ) + +# item = hr.Li() +# item.append("And this is a ") +# item.append( hr.A("http://google.com", "link") ) +# item.append("to google") + +# list.append(item) + +# body.append(list) + +# page.append(body) + +# render_page(page, "test_html_output7.html") + +# # Step 8 and 9 +# ############## + +# page = hr.Html() + + +# head = hr.Head() +# head.append( hr.Meta(charset="UTF-8") ) +# head.append(hr.Title("PythonClass = Revision 1087:")) + +# page.append(head) + +# body = hr.Body() + +# body.append( hr.H(2, "PythonClass - Example") ) + +# body.append(hr.P("Here is a paragraph of text -- there could be more of them, " +# "but this is enough to show that we can do some text", +# style="text-align: center; font-style: oblique;")) + +# body.append(hr.Hr()) + +# list = hr.Ul(id="TheList", style="line-height:200%") + +# list.append( hr.Li("The first item in a list") ) +# list.append( hr.Li("This is the second item", style="color: red") ) + +# item = hr.Li() +# item.append("And this is a ") +# item.append( hr.A("http://google.com", "link") ) +# item.append("to google") + +# list.append(item) + +# body.append(list) + +# page.append(body) + +# render_page(page, "test_html_output8.html") diff --git a/students/FyfyNguyen/session07/sample_html.html b/students/FyfyNguyen/session07/sample_html.html new file mode 100644 index 00000000..9c2e675d --- /dev/null +++ b/students/FyfyNguyen/session07/sample_html.html @@ -0,0 +1,27 @@ + + +
+ ++ Here is a paragraph of text -- there could be more of them, but this is enough to show that we can do some text +
+