From 9b7e31bfe2032ab131018b8207430f93eb452809 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 24 Aug 2021 19:29:10 +0900 Subject: [PATCH 01/98] Amended .gitignore to ignore venv folder. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 68ca39690..7153de9fe 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ tex2pdf* .coverage .idea .vscode +venv/ 02_crowsnest/crowsnest.py 03_picnic/picnic.py 04_jump_the_five/jump.py From 5686ffe75f52765c5a8bc69af38d5cf715ac31fa Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 25 Aug 2021 13:49:16 +0900 Subject: [PATCH 02/98] Added my hello.py file for Chapter 1 --- 01_hello/hello.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 01_hello/hello.py diff --git a/01_hello/hello.py b/01_hello/hello.py new file mode 100755 index 000000000..13827e9e8 --- /dev/null +++ b/01_hello/hello.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# Purpose: Say hello + +import argparse + + +def main(): + parser = argparse.ArgumentParser(description='Say hello') + parser.add_argument('-n', + '--name', + metavar='name', + default='World', + help='Name to greet') + args = parser.parse_args() + + print(f"Hello, {args.name}!") + + +if __name__ == '__main__': + main() From bc0283f7f6ac290c86df57b17bddaaae6cc4cb5a Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 25 Aug 2021 14:01:15 +0900 Subject: [PATCH 03/98] Refactored hello.py to put argparse stuff into a function. --- 01_hello/hello.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/01_hello/hello.py b/01_hello/hello.py index 13827e9e8..b7af8e45d 100755 --- a/01_hello/hello.py +++ b/01_hello/hello.py @@ -4,15 +4,15 @@ import argparse -def main(): +def get_args(): parser = argparse.ArgumentParser(description='Say hello') - parser.add_argument('-n', - '--name', - metavar='name', - default='World', - help='Name to greet') - args = parser.parse_args() + parser.add_argument('-n', '--name', metavar='name', + default='World', help='Name to greet') + return parser.parse_args() + +def main(): + args = get_args() print(f"Hello, {args.name}!") From 889197e964466cbee9142780acba69b74414d895 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 25 Aug 2021 14:15:10 +0900 Subject: [PATCH 04/98] HELLO: After linting (added docstrings). --- 01_hello/hello.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/01_hello/hello.py b/01_hello/hello.py index b7af8e45d..f1aa9f6fd 100755 --- a/01_hello/hello.py +++ b/01_hello/hello.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 -# Purpose: Say hello - +""" +Author: the infamous herjazz +Purpose: Say hello +""" import argparse def get_args(): + """ Get command-line arguments """ parser = argparse.ArgumentParser(description='Say hello') parser.add_argument('-n', '--name', metavar='name', default='World', help='Name to greet') @@ -12,6 +15,7 @@ def get_args(): def main(): + """ It comes from Africa, ca, ca, ca """ args = get_args() print(f"Hello, {args.name}!") From 87f56643c4ebe13682ac860382711a5170cb5584 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 25 Aug 2021 19:57:48 +0900 Subject: [PATCH 05/98] Amended .gitignore to allow crowsnest.py to be added. --- .gitignore | 2 +- 02_crowsnest/crowsnest.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100755 02_crowsnest/crowsnest.py diff --git a/.gitignore b/.gitignore index 7153de9fe..d6086aa0e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ tex2pdf* .idea .vscode venv/ -02_crowsnest/crowsnest.py +# 02_crowsnest/crowsnest.py 03_picnic/picnic.py 04_jump_the_five/jump.py 05_howler/howler.py diff --git a/02_crowsnest/crowsnest.py b/02_crowsnest/crowsnest.py new file mode 100755 index 000000000..421cea85d --- /dev/null +++ b/02_crowsnest/crowsnest.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-25 +Purpose: Print a sentence with a word taken from command line +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description="Crow's Nest -- choose the correct article", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('word', metavar='word', help='A word') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Running with it """ + + args = get_args() + word = args.word + char = word[0].lower() + article = 'an' if char in 'aeiou' else 'a' + print(f"Ahoy, Captain, {article} {word} off the larboard bow!") + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From cf7462d94031c842ed53ac6a24f6a3b151e557e1 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 11:56:51 +0900 Subject: [PATCH 06/98] CROWS: Changed this file and tests for match 'going further' section. --- 02_crowsnest/crowsnest.py | 11 ++++++++--- 02_crowsnest/test.py | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/02_crowsnest/crowsnest.py b/02_crowsnest/crowsnest.py index 421cea85d..09e8e9f60 100755 --- a/02_crowsnest/crowsnest.py +++ b/02_crowsnest/crowsnest.py @@ -18,6 +18,8 @@ def get_args(): parser.add_argument('word', metavar='word', help='A word') + parser.add_argument('--starboard', default=False, action='store_true') + return parser.parse_args() @@ -27,9 +29,12 @@ def main(): args = get_args() word = args.word - char = word[0].lower() - article = 'an' if char in 'aeiou' else 'a' - print(f"Ahoy, Captain, {article} {word} off the larboard bow!") + side = 'starboard' if args.starboard else 'larboard' + char = word[0] + article = 'an' if char.lower() in 'aeiou' else 'a' + if char.isupper(): + article = article.title() + print(f"Ahoy, Captain, {article} {word} off the {side} bow!") # -------------------------------------------------- diff --git a/02_crowsnest/test.py b/02_crowsnest/test.py index d4e25c6c7..a9c5f720a 100755 --- a/02_crowsnest/test.py +++ b/02_crowsnest/test.py @@ -43,11 +43,11 @@ def test_consonant(): # -------------------------------------------------- def test_consonant_upper(): - """brigantine -> a Brigatine""" + """brigantine -> A Brigantine""" for word in consonant_words: out = getoutput(f'{prg} {word.title()}') - assert out.strip() == template.format('a', word.title()) + assert out.strip() == template.format('A', word.title()) # -------------------------------------------------- @@ -61,8 +61,17 @@ def test_vowel(): # -------------------------------------------------- def test_vowel_upper(): - """octopus -> an Octopus""" + """octopus -> An Octopus""" for word in vowel_words: out = getoutput(f'{prg} {word.upper()}') - assert out.strip() == template.format('an', word.upper()) + assert out.strip() == template.format('An', word.upper()) + + +# -------------------------------------------------- +def test_starboard(): + """starboard""" + + flag = '--starboard' + out = getoutput(f'{prg} octopus {flag}') + assert out.strip() == 'Ahoy, Captain, an octopus off the starboard bow!' From d3729a4f640db8d60a5cd8fec490eaef76a21924 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 13:32:29 +0900 Subject: [PATCH 07/98] CROWS: Added test and code for final part of Ch2 'Going futher'. --- 02_crowsnest/crowsnest.py | 11 ++++++++++- 02_crowsnest/test.py | 12 +++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/02_crowsnest/crowsnest.py b/02_crowsnest/crowsnest.py index 09e8e9f60..fda936452 100755 --- a/02_crowsnest/crowsnest.py +++ b/02_crowsnest/crowsnest.py @@ -23,12 +23,21 @@ def get_args(): return parser.parse_args() +# -------------------------------------------------- +def validate_word(supplied_word: str) -> str: + """Check first character of supplied word is a letter""" + + if not supplied_word[0].isalpha(): + raise ValueError + return supplied_word + + # -------------------------------------------------- def main(): """ Running with it """ args = get_args() - word = args.word + word = validate_word(args.word) side = 'starboard' if args.starboard else 'larboard' char = word[0] article = 'an' if char.lower() in 'aeiou' else 'a' diff --git a/02_crowsnest/test.py b/02_crowsnest/test.py index a9c5f720a..57e34bc25 100755 --- a/02_crowsnest/test.py +++ b/02_crowsnest/test.py @@ -2,7 +2,7 @@ """tests for crowsnest.py""" import os -from subprocess import getstatusoutput, getoutput +from subprocess import getstatusoutput, getoutput, CalledProcessError prg = './crowsnest.py' consonant_words = [ @@ -75,3 +75,13 @@ def test_starboard(): flag = '--starboard' out = getoutput(f'{prg} octopus {flag}') assert out.strip() == 'Ahoy, Captain, an octopus off the starboard bow!' + + +# -------------------------------------------------- +def test_first_char_numeric(): + """e.g. 02test - throw exception""" + + word = '02test' + out = getoutput(f'{prg} {word}') + + assert out.endswith("ValueError") From 0b54964180f25fa775c92b0c6865898caa932ba0 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 14:09:42 +0900 Subject: [PATCH 08/98] Amended .gitignore to allow picnic.py to be added. --- .gitignore | 2 +- 03_picnic/picnic.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100755 03_picnic/picnic.py diff --git a/.gitignore b/.gitignore index d6086aa0e..bc25fc25e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ tex2pdf* .vscode venv/ # 02_crowsnest/crowsnest.py -03_picnic/picnic.py +# 03_picnic/picnic.py 04_jump_the_five/jump.py 05_howler/howler.py 06_wc/wc.py diff --git a/03_picnic/picnic.py b/03_picnic/picnic.py new file mode 100755 index 000000000..f26716031 --- /dev/null +++ b/03_picnic/picnic.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-26 +Purpose: Print a list (possibly sorted) of items to bring to a picnic +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Picnic game', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('item', + metavar='str', + nargs='+', + help='Item(s) to bring') + + parser.add_argument('-s', + '--sorted', + help='Sort the items', + default=False, + action='store_true') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Main part of the prog """ + + args = get_args() + items = args.item + if len(items) == 1: + picnic_items = items[0] + else: + if args.sorted: + items.sort() + items_except_last = ', '.join(items[:-1]) + final_item = f" and {items[-1]}" if len(items) == 2 else f", and {items[-1]}" + picnic_items = items_except_last + final_item + print(f"You are bringing {picnic_items}.") + +# -------------------------------------------------- +if __name__ == '__main__': + main() From d1d555aa64a3d24381afc923fc449dbc8f24b56a Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 14:35:42 +0900 Subject: [PATCH 09/98] PICNIC: After first refactor. --- 03_picnic/picnic.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/03_picnic/picnic.py b/03_picnic/picnic.py index f26716031..57c118728 100755 --- a/03_picnic/picnic.py +++ b/03_picnic/picnic.py @@ -36,13 +36,18 @@ def main(): args = get_args() items = args.item - if len(items) == 1: + num_items = len(items) + + if args.sorted: + items.sort() + + if num_items == 1: picnic_items = items[0] + elif num_items == 2: + picnic_items = ' and '.join(items) else: - if args.sorted: - items.sort() items_except_last = ', '.join(items[:-1]) - final_item = f" and {items[-1]}" if len(items) == 2 else f", and {items[-1]}" + final_item = f", and {items[-1]}" picnic_items = items_except_last + final_item print(f"You are bringing {picnic_items}.") From 85ca51bae6628440e3bc8386c882cf5d1b9a7e17 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 14:37:27 +0900 Subject: [PATCH 10/98] PICNIC: After linting/style checking. --- 03_picnic/picnic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/03_picnic/picnic.py b/03_picnic/picnic.py index 57c118728..92f6a9067 100755 --- a/03_picnic/picnic.py +++ b/03_picnic/picnic.py @@ -51,6 +51,7 @@ def main(): picnic_items = items_except_last + final_item print(f"You are bringing {picnic_items}.") + # -------------------------------------------------- if __name__ == '__main__': main() From e0e6a54738750b979b7048c672b862b0b317d0b3 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 14:51:20 +0900 Subject: [PATCH 11/98] PICNIC: Added test and code to allow for no Oxford comma. --- 03_picnic/picnic.py | 8 ++++++-- 03_picnic/test.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/03_picnic/picnic.py b/03_picnic/picnic.py index 92f6a9067..bd72142dc 100755 --- a/03_picnic/picnic.py +++ b/03_picnic/picnic.py @@ -24,7 +24,11 @@ def get_args(): parser.add_argument('-s', '--sorted', help='Sort the items', - default=False, + action='store_true') + + + parser.add_argument('--no_oxford', + help='Don\'t use an Oxford comma', action='store_true') return parser.parse_args() @@ -47,7 +51,7 @@ def main(): picnic_items = ' and '.join(items) else: items_except_last = ', '.join(items[:-1]) - final_item = f", and {items[-1]}" + final_item = f" and {items[-1]}" if args.no_oxford else f", and {items[-1]}" picnic_items = items_except_last + final_item print(f"You are bringing {picnic_items}.") diff --git a/03_picnic/test.py b/03_picnic/test.py index 281fab552..fd49b0f49 100755 --- a/03_picnic/test.py +++ b/03_picnic/test.py @@ -66,3 +66,13 @@ def test_more_than_two_sorted(): out = getoutput(f'{prg} {arg} --sorted') expected = ('You are bringing apples, bananas, cherries, and dates.') assert out.strip() == expected + + +# -------------------------------------------------- +def test_no_oxford_comma(): + """more than two items no oxford comma""" + + arg = 'bananas apples dates cherries' + out = getoutput(f'{prg} {arg} --no_oxford') + expected = ('You are bringing bananas, apples, dates and cherries.') + assert out.strip() == expected From cce67d8f6e00212647d50b961b5601736ab263e9 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 15:00:51 +0900 Subject: [PATCH 12/98] PICNIC: Added type info to positional argument. --- 03_picnic/picnic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03_picnic/picnic.py b/03_picnic/picnic.py index bd72142dc..e7e7f11e2 100755 --- a/03_picnic/picnic.py +++ b/03_picnic/picnic.py @@ -19,6 +19,7 @@ def get_args(): parser.add_argument('item', metavar='str', nargs='+', + type=str, help='Item(s) to bring') parser.add_argument('-s', @@ -26,7 +27,6 @@ def get_args(): help='Sort the items', action='store_true') - parser.add_argument('--no_oxford', help='Don\'t use an Oxford comma', action='store_true') From 13668630a217982c60016d7f4db07463b15224b5 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 15:08:17 +0900 Subject: [PATCH 13/98] Amended .gitignore to allow jump.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bc25fc25e..7c502fae1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ tex2pdf* venv/ # 02_crowsnest/crowsnest.py # 03_picnic/picnic.py -04_jump_the_five/jump.py +# 04_jump_the_five/jump.py 05_howler/howler.py 06_wc/wc.py 07_gashlycrumb/gashlycrumb.py From 0917031425b9b6d48abd1a025470332c778281f2 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 15:42:16 +0900 Subject: [PATCH 14/98] Added my passing jump.py. --- 04_jump_the_five/jump.py | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 04_jump_the_five/jump.py diff --git a/04_jump_the_five/jump.py b/04_jump_the_five/jump.py new file mode 100755 index 000000000..20d42823c --- /dev/null +++ b/04_jump_the_five/jump.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-26 +Purpose: Use 'jump-the-five' encoding to unscramble phone numbers +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Jump the Five', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='str', + help='Input text') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Main prog""" + + args = get_args() + encoded_text = args.text + + jump_dict = { + "1": "9", + "2": "8", + "3": "7", + "4": "6", + "5": "0", + "6": "4", + "7": "3", + "8": "2", + "9": "1", + "0": "5" + } + + decoded_text = '' + + for char in encoded_text: + decoded_text += jump_dict.get(char, char) + + print(decoded_text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 5752bf059befb4b153a4282674d7628d4953df56 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 17:56:52 +0900 Subject: [PATCH 15/98] JUMP: Linted and added str.translate method as a comment. --- 04_jump_the_five/jump.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/04_jump_the_five/jump.py b/04_jump_the_five/jump.py index 20d42823c..4b3e86129 100755 --- a/04_jump_the_five/jump.py +++ b/04_jump_the_five/jump.py @@ -16,9 +16,7 @@ def get_args(): description='Jump the Five', formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('text', - metavar='str', - help='Input text') + parser.add_argument('text', metavar='str', help='Input text') return parser.parse_args() @@ -50,6 +48,9 @@ def main(): print(decoded_text) + # Other possible solution - use str.translate + # print(encoded_text.translate(str.maketrans(jump_dict))) + # -------------------------------------------------- if __name__ == '__main__': From 253ac2cae0cc35109c73203ac174d3e2de723126 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 18:40:20 +0900 Subject: [PATCH 16/98] Amended .gitignore to allow howler.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7c502fae1..96b214482 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ venv/ # 02_crowsnest/crowsnest.py # 03_picnic/picnic.py # 04_jump_the_five/jump.py -05_howler/howler.py +# 05_howler/howler.py 06_wc/wc.py 07_gashlycrumb/gashlycrumb.py 08_apples_and_bananas/apples.py From 1a9d5e1c569ee08e220086d95fd31839e63b7875 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 18:41:20 +0900 Subject: [PATCH 17/98] HOWLER: First try - passed all tests. --- 05_howler/howler.py | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 05_howler/howler.py diff --git a/05_howler/howler.py b/05_howler/howler.py new file mode 100755 index 000000000..3da159312 --- /dev/null +++ b/05_howler/howler.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-26 +Purpose: Return a string in UPPERCASE, either from an arg or a file and output + to stdout or a file. +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Howler (upper-cases input)', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='str', + help='Input string or file') + + parser.add_argument('-o', + '--outfile', + help='Output filename', + metavar='str', + type=str, + default='') + + return parser.parse_args() + + +# -------------------------------------------------- +def get_file_contents(filename: str) -> str: + """ Receive a filename and return its contents """ + + with open(filename, "rt") as f: + text = f.read().rstrip() + + return text + + +# -------------------------------------------------- +def write_message(filename: str, contents: str) -> None: + """ Write contents of a message to a file """ + + with open(filename, "wt") as f: + f.write(contents) + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + message = args.text + if os.path.isfile(message): + message = get_file_contents(message) + howled_message = message.upper() + if args.outfile: + write_message(args.outfile, howled_message) + else: + print(howled_message) + +# print(f'str_arg = "{str_arg}"') +# print(f'int_arg = "{int_arg}"') +# print('file_arg = "{}"'.format(file_arg.name if file_arg else '')) +# print(f'flag_arg = "{flag_arg}"') +# print(f'positional = "{pos_arg}"') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 372903e33925e140390b02fc5671757baeb62ce5 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 18:49:21 +0900 Subject: [PATCH 18/98] HOWLER: After linting. --- 05_howler/howler.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/05_howler/howler.py b/05_howler/howler.py index 3da159312..aeb3c4086 100755 --- a/05_howler/howler.py +++ b/05_howler/howler.py @@ -18,9 +18,7 @@ def get_args(): description='Howler (upper-cases input)', formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('text', - metavar='str', - help='Input string or file') + parser.add_argument('text', metavar='str', help='Input string or file') parser.add_argument('-o', '--outfile', @@ -36,8 +34,8 @@ def get_args(): def get_file_contents(filename: str) -> str: """ Receive a filename and return its contents """ - with open(filename, "rt") as f: - text = f.read().rstrip() + with open(filename, "rt", encoding="utf-8") as infile: + text = infile.read().rstrip() return text @@ -46,8 +44,8 @@ def get_file_contents(filename: str) -> str: def write_message(filename: str, contents: str) -> None: """ Write contents of a message to a file """ - with open(filename, "wt") as f: - f.write(contents) + with open(filename, "wt", encoding="utf-8") as outfile: + outfile.write(contents) # -------------------------------------------------- @@ -56,20 +54,17 @@ def main(): args = get_args() message = args.text + if os.path.isfile(message): message = get_file_contents(message) + howled_message = message.upper() + if args.outfile: write_message(args.outfile, howled_message) else: print(howled_message) -# print(f'str_arg = "{str_arg}"') -# print(f'int_arg = "{int_arg}"') -# print('file_arg = "{}"'.format(file_arg.name if file_arg else '')) -# print(f'flag_arg = "{flag_arg}"') -# print(f'positional = "{pos_arg}"') - # -------------------------------------------------- if __name__ == '__main__': From 25d126a8916a4bbe9f60f22d3e2da194cb4850a4 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 19:33:30 +0900 Subject: [PATCH 19/98] HOWLER: Added lowmem version, adjusted test.py to test it. --- 05_howler/howler_lowmem.py | 58 ++++++++++++++++++++++++++++++++++++++ 05_howler/test.py | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100755 05_howler/howler_lowmem.py diff --git a/05_howler/howler_lowmem.py b/05_howler/howler_lowmem.py new file mode 100755 index 000000000..4964d7fef --- /dev/null +++ b/05_howler/howler_lowmem.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-26 +Purpose: Return a string in UPPERCASE, either from an arg or a file and output + to stdout or a file. +""" + +import argparse +import io +import os +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Howler (upper-cases input)', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='str', help='Input string or file') + + parser.add_argument('-o', + '--outfile', + help='Output filename', + metavar='str', + type=str, + default='') + + args = parser.parse_args() + + if os.path.isfile(args.text): + # QUERY: Don't we need to close this file? + args.text = open(args.text) + else: + # using io when unsure if arg is filename or string + # (using \n to look like lines of output from a file + args.text = io.StringIO(args.text + '\n') + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + out_fh = open(args.outfile, 'wt', encoding='utf-8') if args.outfile else sys.stdout + for line in args.text: + out_fh.write(line.upper()) + out_fh.close() + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/05_howler/test.py b/05_howler/test.py index 1c04934c8..bcd2f0b48 100755 --- a/05_howler/test.py +++ b/05_howler/test.py @@ -7,7 +7,7 @@ import string from subprocess import getstatusoutput, getoutput -prg = './howler.py' +prg = './howler_lowmem.py' # -------------------------------------------------- From 3584d2a14a93f98fb9ab91e23a80a0230bdbfc47 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 26 Aug 2021 19:37:45 +0900 Subject: [PATCH 20/98] HOWLER: Linted lowmem version. --- 05_howler/howler_lowmem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/05_howler/howler_lowmem.py b/05_howler/howler_lowmem.py index 4964d7fef..4e8a4b4b5 100755 --- a/05_howler/howler_lowmem.py +++ b/05_howler/howler_lowmem.py @@ -33,7 +33,7 @@ def get_args(): if os.path.isfile(args.text): # QUERY: Don't we need to close this file? - args.text = open(args.text) + args.text = open(args.text, 'r', encoding='utf-8') else: # using io when unsure if arg is filename or string # (using \n to look like lines of output from a file @@ -47,7 +47,8 @@ def main(): """ Main prog """ args = get_args() - out_fh = open(args.outfile, 'wt', encoding='utf-8') if args.outfile else sys.stdout + out_fh = open(args.outfile, 'wt', + encoding='utf-8') if args.outfile else sys.stdout for line in args.text: out_fh.write(line.upper()) out_fh.close() From 273040a94d7e2b9563aa66eace982e87925fa23c Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 27 Aug 2021 13:51:44 +0900 Subject: [PATCH 21/98] Amended .gitignore to allow wc.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 96b214482..207efdbf3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ venv/ # 03_picnic/picnic.py # 04_jump_the_five/jump.py # 05_howler/howler.py -06_wc/wc.py +# 06_wc/wc.py 07_gashlycrumb/gashlycrumb.py 08_apples_and_bananas/apples.py 09_abuse/abuse.py From 8ee18064ab3f26db43375fd7026068532be7431c Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 27 Aug 2021 13:52:21 +0900 Subject: [PATCH 22/98] WC: First try - passed all tests. --- 06_wc/wc.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 06_wc/wc.py diff --git a/06_wc/wc.py b/06_wc/wc.py new file mode 100755 index 000000000..0751847c2 --- /dev/null +++ b/06_wc/wc.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-26 +Purpose: Make a python version of the command 'wc' +""" + +import argparse +import io +import os +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Emulate wc (word count)', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', + metavar='FILE', + nargs='*', + type=argparse.FileType('rt', encoding='utf-8'), + default=[sys.stdin], + help='Input file(s)') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Main prog here """ + + args = get_args() + + # print(*args.file) + + total_lines, total_words, total_bytes = 0, 0, 0 + + for fh in args.file: + num_lines, num_words, num_bytes = 0, 0, 0 + for line in fh: + num_lines += 1 + num_words += len(line.split()) + num_bytes += len(line) + total_lines += num_lines + total_words += num_words + total_bytes += num_bytes + print(f"{num_lines:8}{num_words:8}{num_bytes:8} {fh.name}") + + if len(args.file) > 1: + print(f"{total_lines:8}{total_words:8}{total_bytes:8} total") + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 870a5761f4a00790cbb19b66b7714c0eee69895e Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 27 Aug 2021 14:02:06 +0900 Subject: [PATCH 23/98] WC: Closed all open files in the for loop. --- 06_wc/wc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/06_wc/wc.py b/06_wc/wc.py index 0751847c2..b188b9520 100755 --- a/06_wc/wc.py +++ b/06_wc/wc.py @@ -6,8 +6,8 @@ """ import argparse -import io -import os +# import io +# import os import sys @@ -49,6 +49,7 @@ def main(): total_words += num_words total_bytes += num_bytes print(f"{num_lines:8}{num_words:8}{num_bytes:8} {fh.name}") + fh.close() if len(args.file) > 1: print(f"{total_lines:8}{total_words:8}{total_bytes:8} total") From 5c4231c19ff96c4fa3664d9ff0782ae9f6b0961c Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 27 Aug 2021 16:47:12 +0900 Subject: [PATCH 24/98] WC: Added handler and tests for -c -l and -w options. --- 06_wc/test.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 06_wc/wc.py | 47 ++++++++++++++++++++++++++++--- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/06_wc/test.py b/06_wc/test.py index f9ebc9dea..fb13c6854 100755 --- a/06_wc/test.py +++ b/06_wc/test.py @@ -105,3 +105,79 @@ def test_stdin(): rv, out = getstatusoutput(f'{prg} < {fox}') assert rv == 0 assert out.rstrip() == ' 1 9 45 ' + + +# -------------------------------------------------- +def test_oneline_show_lines_only(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -l') + assert rv == 0 + assert out.rstrip() == ' 1 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_oneline_show_words_only(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -w') + assert rv == 0 + assert out.rstrip() == ' 9 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_oneline_show_bytes_only(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -c') + assert rv == 0 + assert out.rstrip() == ' 45 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_oneline_show_lines_words(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -wl') + assert rv == 0 + assert out.rstrip() == ' 1 9 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_oneline_show_lines_chars(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -lc') + assert rv == 0 + assert out.rstrip() == ' 1 45 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_oneline_show_words_chars(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -wc') + assert rv == 0 + assert out.rstrip() == ' 9 45 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_oneline_show_lines_words_chars(): + """Test on fox""" + + rv, out = getstatusoutput(f'{prg} {fox} -wlc') + assert rv == 0 + assert out.rstrip() == ' 1 9 45 ../inputs/fox.txt' + + +# -------------------------------------------------- +def test_more_lines_chars_only(): + """Test on more than one file""" + + rv, out = getstatusoutput(f'{prg} {fox} {sonnet} -lc') + expected = (' 1 45 ../inputs/fox.txt\n' + ' 17 661 ../inputs/sonnet-29.txt\n' + ' 18 706 total') + assert rv == 0 + assert out.rstrip() == expected + diff --git a/06_wc/wc.py b/06_wc/wc.py index b188b9520..743f93e34 100755 --- a/06_wc/wc.py +++ b/06_wc/wc.py @@ -26,16 +26,52 @@ def get_args(): default=[sys.stdin], help='Input file(s)') + parser.add_argument('-c', + '--chars', + help='Show number of bytes', + action='store_true') + + parser.add_argument('-w', + '--words', + help='Show number of words', + action='store_true') + + parser.add_argument('-l', + '--lines', + help='Show number of lines', + action='store_true') + return parser.parse_args() +# -------------------------------------------------- +def select_data(first_data: int, second_data: int, third_data: int, + flags: str) -> None: + """ Choose what to print, depending on flags """ + if not flags: + return f"{first_data:8}{second_data:8}{third_data:8}" + information = '' + if 'l' in flags: + information += f"{first_data:8}" + if 'w' in flags: + information += f"{second_data:8}" + if 'c' in flags: + information += f"{third_data:8}" + return information + + # -------------------------------------------------- def main(): """ Main prog here """ args = get_args() - - # print(*args.file) + flags = '' + if args.lines: + flags += "l" + if args.words: + flags += "w" + if args.chars: + flags += "c" total_lines, total_words, total_bytes = 0, 0, 0 @@ -48,11 +84,14 @@ def main(): total_lines += num_lines total_words += num_words total_bytes += num_bytes - print(f"{num_lines:8}{num_words:8}{num_bytes:8} {fh.name}") + # Check for presence of flags, and print accordingly + print(select_data(num_lines, num_words, num_bytes, flags), fh.name) fh.close() if len(args.file) > 1: - print(f"{total_lines:8}{total_words:8}{total_bytes:8} total") + print( + select_data(total_lines, total_words, total_bytes, flags) + + ' total') # -------------------------------------------------- From e636d617fa73ec1000b37f003cbeeffe65629d8c Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 09:03:38 +0900 Subject: [PATCH 25/98] GASH: Added my first effort - fails bad file test because of how I handled file input. --- 07_gashlycrumb/gashlycrumb.py | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100755 07_gashlycrumb/gashlycrumb.py diff --git a/07_gashlycrumb/gashlycrumb.py b/07_gashlycrumb/gashlycrumb.py new file mode 100755 index 000000000..51da0c657 --- /dev/null +++ b/07_gashlycrumb/gashlycrumb.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-30 +Purpose: Extract a line of text from Gashlycrumb that matches letter(s) +supplied on command line. Takes an optional argument to use a different source +file. +""" + +import argparse +import os +import sys +import string + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Gashlycrumb', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('letter', + metavar='letter', + nargs='+', + help='Letter(s)') + + parser.add_argument('-f', + '--file', + help='Input file', + metavar='FILE', + type=str, + default='./gashlycrumb.txt') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Main program """ + + args = get_args() + letter_choices = args.letter + + # Extract text line-by-line from file + try: + with open(args.file, 'r', encoding='utf-8') as src_file: + text = src_file.readlines() + text = [line.rstrip() for line in text] + except FileNotFoundError: + print("No file matches") + # sys.exit() + else: + # Make dict that alphabetizes a dict extracting line for each value + keys = string.ascii_lowercase + letter_dict = {k: v for k, v in zip(keys, text)} + + for letter in letter_choices: + print(letter_dict.get(letter.lower(), f'I do not know "{letter}".')) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 8bc5d0d4f91766670ae0b76f9c5a0234569524d3 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 09:14:14 +0900 Subject: [PATCH 26/98] GASH: Amended to drop need for string.ascii. --- 07_gashlycrumb/gashlycrumb.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/07_gashlycrumb/gashlycrumb.py b/07_gashlycrumb/gashlycrumb.py index 51da0c657..bc04f7f1a 100755 --- a/07_gashlycrumb/gashlycrumb.py +++ b/07_gashlycrumb/gashlycrumb.py @@ -9,8 +9,8 @@ import argparse import os -import sys -import string +# import sys +# import string # -------------------------------------------------- @@ -53,11 +53,12 @@ def main(): # sys.exit() else: # Make dict that alphabetizes a dict extracting line for each value - keys = string.ascii_lowercase - letter_dict = {k: v for k, v in zip(keys, text)} + # keys = string.ascii_lowercase + # letter_dict = {k: v for k, v in zip(keys, text)} + letter_dict = {line[0].upper(): line for line in text} for letter in letter_choices: - print(letter_dict.get(letter.lower(), f'I do not know "{letter}".')) + print(letter_dict.get(letter.upper(), f'I do not know "{letter}".')) # -------------------------------------------------- From 147b7a1ae4ae1a01f9d708934f470c89b0ae6046 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 09:34:29 +0900 Subject: [PATCH 27/98] Amended .gitignore to allow gashlycrumb.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 207efdbf3..3d37e9027 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ venv/ # 04_jump_the_five/jump.py # 05_howler/howler.py # 06_wc/wc.py -07_gashlycrumb/gashlycrumb.py +# 07_gashlycrumb/gashlycrumb.py 08_apples_and_bananas/apples.py 09_abuse/abuse.py 10_telephone/telephone.py From 3925cd6873f07caf9e6b1af68f4e3557440a6a3d Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 09:35:04 +0900 Subject: [PATCH 28/98] GASH: Some tidying up. --- 07_gashlycrumb/gashlycrumb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/07_gashlycrumb/gashlycrumb.py b/07_gashlycrumb/gashlycrumb.py index bc04f7f1a..167a4c4ca 100755 --- a/07_gashlycrumb/gashlycrumb.py +++ b/07_gashlycrumb/gashlycrumb.py @@ -47,7 +47,7 @@ def main(): try: with open(args.file, 'r', encoding='utf-8') as src_file: text = src_file.readlines() - text = [line.rstrip() for line in text] + # text = [line.rstrip() for line in text] except FileNotFoundError: print("No file matches") # sys.exit() @@ -55,7 +55,7 @@ def main(): # Make dict that alphabetizes a dict extracting line for each value # keys = string.ascii_lowercase # letter_dict = {k: v for k, v in zip(keys, text)} - letter_dict = {line[0].upper(): line for line in text} + letter_dict = {line[0].upper(): line.rstrip() for line in text} for letter in letter_choices: print(letter_dict.get(letter.upper(), f'I do not know "{letter}".')) From 6e059208b77e6918aac1d43b606e11501b79ed03 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 09:35:36 +0900 Subject: [PATCH 29/98] GASH_INTER: My interactive version. --- 07_gashlycrumb/gashly_interactive.py | 62 ++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 07_gashlycrumb/gashly_interactive.py diff --git a/07_gashlycrumb/gashly_interactive.py b/07_gashlycrumb/gashly_interactive.py new file mode 100755 index 000000000..2f4bb5fca --- /dev/null +++ b/07_gashlycrumb/gashly_interactive.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-30 +Purpose: Extract a line of text from Gashlycrumb that matches letter(s) +supplied on command line. Takes an optional argument to use a different source +file. +""" + +import argparse +import os + +# import sys +# import string + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Gashlycrumb', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-f', + '--file', + help='Input file', + metavar='FILE', + type=str, + default='./gashlycrumb.txt') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Main program """ + + args = get_args() + + # Extract text line-by-line from file + try: + with open(args.file, 'r', encoding='utf-8') as src_file: + text = src_file.readlines() + except FileNotFoundError: + print("No file matches") + else: + letter_dict = {line[0].upper(): line.rstrip() for line in text} + + while True: + user_choice = input("Please provide a letter [! to quit]: ") + + if user_choice == "!": + print("Bye") + break + + print(letter_dict.get(user_choice.upper(), f'I do not know "{user_choice}".')) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 40cae03825a25c80822ed2f4344647c0b569ae5e Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 19:40:45 +0900 Subject: [PATCH 30/98] Amended .gitignore to allow apple.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3d37e9027..d5233b498 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ venv/ # 05_howler/howler.py # 06_wc/wc.py # 07_gashlycrumb/gashlycrumb.py -08_apples_and_bananas/apples.py +# 08_apples_and_bananas/apples.py 09_abuse/abuse.py 10_telephone/telephone.py 11_bottles_of_beer/bottles.py From d380fb367b5e9c12491a264e4f7b433b45bde689 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 19:41:42 +0900 Subject: [PATCH 31/98] APPLES: My first effort using str.replace(). --- 08_apples_and_bananas/apples.py | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 08_apples_and_bananas/apples.py diff --git a/08_apples_and_bananas/apples.py b/08_apples_and_bananas/apples.py new file mode 100755 index 000000000..fe12f08b8 --- /dev/null +++ b/08_apples_and_bananas/apples.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-31 +Purpose: A program which substitutes vowels in text with a supplied vowel +(default: 'a') +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Apples and bananas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-v', + '--vowel', + help='The vowel to substitute', + metavar='vowel', + type=str, + choices=['a', 'e', 'i', 'o', 'u'], + default='a') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + + vowels = 'AEIOUaeiou' + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read() + + changed_text = '' + + for letter in args.text: + if letter in vowels: + if letter.isupper(): + args.vowel = args.vowel.upper() + letter = letter.replace(letter, args.vowel) + changed_text += letter + + print(changed_text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From ca2cb2cb7ff4bfd7ad61a3f7a50a1758baab7b81 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 19:55:58 +0900 Subject: [PATCH 32/98] APPLES_TRANS: Version using str.translate(). --- 08_apples_and_bananas/apples_translate.py | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 08_apples_and_bananas/apples_translate.py diff --git a/08_apples_and_bananas/apples_translate.py b/08_apples_and_bananas/apples_translate.py new file mode 100755 index 000000000..3875c8b53 --- /dev/null +++ b/08_apples_and_bananas/apples_translate.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-31 +Purpose: A program which substitutes vowels in text with a supplied vowel +(default: 'a') +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Apples and bananas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-v', + '--vowel', + help='The vowel to substitute', + metavar='vowel', + type=str, + choices=['a', 'e', 'i', 'o', 'u'], + default='a') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + + vowel_trans_table = {'A': args.vowel.upper(), + 'E': args.vowel.upper(), + 'I': args.vowel.upper(), + 'O': args.vowel.upper(), + 'U': args.vowel.upper(), + 'a': args.vowel, + 'e': args.vowel, + 'i': args.vowel, + 'o': args.vowel, + 'u': args.vowel} + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read() + + changed_text = args.text.translate(str.maketrans(vowel_trans_table)) + + print(changed_text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From f31d69e18e444e9c7f3e91dfdfd1be1b443badf6 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 20:08:10 +0900 Subject: [PATCH 33/98] APPLES: Moved check for file code into get_args(). --- 08_apples_and_bananas/apples.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/08_apples_and_bananas/apples.py b/08_apples_and_bananas/apples.py index fe12f08b8..a784daca8 100755 --- a/08_apples_and_bananas/apples.py +++ b/08_apples_and_bananas/apples.py @@ -28,7 +28,13 @@ def get_args(): choices=['a', 'e', 'i', 'o', 'u'], default='a') - return parser.parse_args() + args = parser.parse_args() + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read().rstrip() + + return args # -------------------------------------------------- @@ -38,11 +44,6 @@ def main(): args = get_args() vowels = 'AEIOUaeiou' - - if os.path.isfile(args.text): - with open(args.text, 'rt', encoding='utf-8') as stream: - args.text = stream.read() - changed_text = '' for letter in args.text: From fb39527771aa4d62a1b05ed047b848088f6cf7be Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 20:25:58 +0900 Subject: [PATCH 34/98] APPLES_TRANS: Updated to use one liner for translation table. --- 08_apples_and_bananas/apples_translate.py | 40 +++++++++++++---------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/08_apples_and_bananas/apples_translate.py b/08_apples_and_bananas/apples_translate.py index 3875c8b53..7fb2374e5 100755 --- a/08_apples_and_bananas/apples_translate.py +++ b/08_apples_and_bananas/apples_translate.py @@ -25,10 +25,16 @@ def get_args(): help='The vowel to substitute', metavar='vowel', type=str, - choices=['a', 'e', 'i', 'o', 'u'], + choices=list('aeiou'), default='a') - return parser.parse_args() + args = parser.parse_args() + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read() + + return args # -------------------------------------------------- @@ -37,24 +43,24 @@ def main(): args = get_args() - vowel_trans_table = {'A': args.vowel.upper(), - 'E': args.vowel.upper(), - 'I': args.vowel.upper(), - 'O': args.vowel.upper(), - 'U': args.vowel.upper(), - 'a': args.vowel, - 'e': args.vowel, - 'i': args.vowel, - 'o': args.vowel, - 'u': args.vowel} + # vowel_trans_table = {'A': args.vowel.upper(), + # 'E': args.vowel.upper(), + # 'I': args.vowel.upper(), + # 'O': args.vowel.upper(), + # 'U': args.vowel.upper(), + # 'a': args.vowel, + # 'e': args.vowel, + # 'i': args.vowel, + # 'o': args.vowel, + # 'u': args.vowel} - if os.path.isfile(args.text): - with open(args.text, 'rt', encoding='utf-8') as stream: - args.text = stream.read() + # One line version below (won't need maketrans call below) + trans = str.maketrans('aeiouAEIOU', + args.vowel * 5 + args.vowel.upper() * 5) - changed_text = args.text.translate(str.maketrans(vowel_trans_table)) + # changed_text = args.text.translate(str.maketrans(vowel_trans_table)) - print(changed_text) + print(args.text.translate(trans)) # -------------------------------------------------- From 7001c5f3322689b4376f82ea79a17ecbca042824 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 20:43:04 +0900 Subject: [PATCH 35/98] APPLES_LIST: Made a version using an internal function to make a list. --- .../apples_list_internal_function.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 08_apples_and_bananas/apples_list_internal_function.py diff --git a/08_apples_and_bananas/apples_list_internal_function.py b/08_apples_and_bananas/apples_list_internal_function.py new file mode 100755 index 000000000..f323a3cb1 --- /dev/null +++ b/08_apples_and_bananas/apples_list_internal_function.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-31 +Purpose: A program which substitutes vowels in text with a supplied vowel +(default: 'a') +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Apples and bananas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-v', + '--vowel', + help='The vowel to substitute', + metavar='vowel', + type=str, + choices=list('aeiou'), + default='a') + + args = parser.parse_args() + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + text = args.text + vowel = args.vowel + + def new_char(c): + """ Returns character depending on conditions """ + # NOTE: This funcation *can* access variables inside main()!!! (e.g. vowel) + return vowel if c in 'aeiou' else vowel.upper() if c in 'AEIOU' else c + + text = ''.join([new_char(letter) for letter in text]) + print(text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 107dbd4ce05ce37d52350aaf6178b0159a8758e4 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 20:55:05 +0900 Subject: [PATCH 36/98] APPLES_MAP: Made a version using map(). --- 08_apples_and_bananas/apples_map.py | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100755 08_apples_and_bananas/apples_map.py diff --git a/08_apples_and_bananas/apples_map.py b/08_apples_and_bananas/apples_map.py new file mode 100755 index 000000000..db32ff790 --- /dev/null +++ b/08_apples_and_bananas/apples_map.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-31 +Purpose: A program which substitutes vowels in text with a supplied vowel +(default: 'a') +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Apples and bananas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-v', + '--vowel', + help='The vowel to substitute', + metavar='vowel', + type=str, + choices=list('aeiou'), + default='a') + + args = parser.parse_args() + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + vowel = args.vowel + text = map( + lambda c: vowel if c in 'aeiou' else vowel.upper() + if c in 'AEIOU' else c, args.text) + + print(''.join(text)) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 979b0130be3a5726a321ceaf77ff6f4e950d486f Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 21:03:24 +0900 Subject: [PATCH 37/98] APPLES_MAP: Afjusted to have map user get_char function. --- 08_apples_and_bananas/apples_map.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/08_apples_and_bananas/apples_map.py b/08_apples_and_bananas/apples_map.py index db32ff790..ed56afc1d 100755 --- a/08_apples_and_bananas/apples_map.py +++ b/08_apples_and_bananas/apples_map.py @@ -43,11 +43,17 @@ def main(): args = get_args() vowel = args.vowel - text = map( - lambda c: vowel if c in 'aeiou' else vowel.upper() - if c in 'AEIOU' else c, args.text) + # text = map( + # lambda c: vowel if c in 'aeiou' else vowel.upper() + # if c in 'AEIOU' else c, args.text) + # print(''.join(text)) - print(''.join(text)) + # Alternatively can use with the new_char function + def new_char(c): + """ Returns character depending on conditions """ + return vowel if c in 'aeiou' else vowel.upper() if c in 'AEIOU' else c + + print(''.join(map(new_char, args.text))) # -------------------------------------------------- From db5f33baacbc666256f33958e0f100316a7bd674 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 31 Aug 2021 21:17:51 +0900 Subject: [PATCH 38/98] APPLES_REGEX: Made a version using regexes. --- 08_apples_and_bananas/apples_regex.py | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100755 08_apples_and_bananas/apples_regex.py diff --git a/08_apples_and_bananas/apples_regex.py b/08_apples_and_bananas/apples_regex.py new file mode 100755 index 000000000..2470baea0 --- /dev/null +++ b/08_apples_and_bananas/apples_regex.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-08-31 +Purpose: A program which substitutes vowels in text with a supplied vowel +(default: 'a') +""" + +import argparse +import re +import os + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Apples and bananas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-v', + '--vowel', + help='The vowel to substitute', + metavar='vowel', + type=str, + choices=list('aeiou'), + default='a') + + args = parser.parse_args() + + if os.path.isfile(args.text): + with open(args.text, 'rt', encoding='utf-8') as stream: + args.text = stream.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + text = args.text + vowel = args.vowel + + text = re.sub('[aeiou]', vowel, text) + text = re.sub('[AEIOU]', vowel.upper(), text) + + print(text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 813d07a9ebae460772aa0fcecb58cd67ed529d1b Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 1 Sep 2021 09:38:42 +0900 Subject: [PATCH 39/98] APPLES_REGEX: Added ability to collapse adjacent matches. --- 08_apples_and_bananas/apples_regex.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/08_apples_and_bananas/apples_regex.py b/08_apples_and_bananas/apples_regex.py index 2470baea0..cd0465f36 100755 --- a/08_apples_and_bananas/apples_regex.py +++ b/08_apples_and_bananas/apples_regex.py @@ -46,8 +46,9 @@ def main(): text = args.text vowel = args.vowel - text = re.sub('[aeiou]', vowel, text) - text = re.sub('[AEIOU]', vowel.upper(), text) + # Added '+' to match one or more vowels and then collapse matches longer than one + text = re.sub('[aeiou]+', vowel, text) + text = re.sub('[AEIOU]+', vowel.upper(), text) print(text) From 146c1de855229db16ce33bada304074f1a73c876 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 1 Sep 2021 20:53:24 +0900 Subject: [PATCH 40/98] Amended .gitignore to allow abuse.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5233b498..a5592e823 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ venv/ # 06_wc/wc.py # 07_gashlycrumb/gashlycrumb.py # 08_apples_and_bananas/apples.py -09_abuse/abuse.py +# 09_abuse/abuse.py 10_telephone/telephone.py 11_bottles_of_beer/bottles.py 12_ransom/ransom.py From aae5ce8ad34d0f4a08a2d427377bd05261097e88 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 1 Sep 2021 20:54:33 +0900 Subject: [PATCH 41/98] ABUSE_TEST: Corrected typo in his test. --- 09_abuse/test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/09_abuse/test.py b/09_abuse/test.py index ff31fc90a..f325ad87e 100755 --- a/09_abuse/test.py +++ b/09_abuse/test.py @@ -90,11 +90,10 @@ def test_01(): def test_02(): """test""" - out = getoutput(f'{prg} --seed 2') + out = getoutput(f'{prg} --seed 2 --number 2') expected = """ You corrupt, detestable beggar! You peevish, foolish gull! -You insatiate, heedless worm! """.strip() assert out.strip() == expected From 5f7da3dbb6e65fcdfe0c27e8f6f91aadd49c5f21 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 1 Sep 2021 20:55:12 +0900 Subject: [PATCH 42/98] ABUSE: First attempt. --- 09_abuse/abuse.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100755 09_abuse/abuse.py diff --git a/09_abuse/abuse.py b/09_abuse/abuse.py new file mode 100755 index 000000000..373ebba5e --- /dev/null +++ b/09_abuse/abuse.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-09-01 +Purpose: A file that fires out random insults taken from a corpus of nouns and +adjectives +""" + +import argparse +import random + +# import textwrap as tw + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Heap abuse', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-a', + '--adjectives', + help='Number of adjectives', + metavar='adjectives', + type=int, + default=2) + + parser.add_argument('-n', + '--number', + help='Number of insults', + metavar='insults', + type=int, + default=1) + + parser.add_argument('-s', + '--seed', + help='Random seed', + metavar='seed', + type=int, + default=None) + + args = parser.parse_args() + + if args.adjectives < 1: + parser.error(f'--adjectives "{args.adjectives}" must be > 0') + + if args.number < 1: + parser.error(f'--number "{args.number}" must be > 0') + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + random.seed(args.seed) + + adjectives = """ + bankrupt base caterwauling corrupt cullionly detestable dishonest false + filthsome filthy foolish foul gross heedless indistinguishable infected + insatiate irksome lascivious lecherous loathsome lubbery old peevish + rascaly rotten ruinous scurilous scurvy slanderous sodden-witted thin-faced + toad-spotted unmannered vile wall-eyed""".split() + + nouns = """ + Judas Satan ape ass barbermonger beggar block boy braggart butt carbuncle + coward coxcomb cur dandy degenerate fiend fishmonger fool gull harpy jack + jolthead knave liar lunatic maw milksop minion ratcatcher recreant rogue + scold slave swine traitor varlet villain worm""".split() + + for _ in range(args.number): + # Using random.sample (rather than random.choices) as the choices are unique + chosen_adjs = ', '.join(random.sample(adjectives, args.adjectives)) + print(f"You {chosen_adjs} {random.choice(nouns)}!") + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From ea89da887f82c1e5a415456103d85a664ef640fb Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 1 Sep 2021 21:46:05 +0900 Subject: [PATCH 43/98] Added a couple of text files for abuse.py. --- 09_abuse/adjectives.txt | 1 + 09_abuse/nouns.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 09_abuse/adjectives.txt create mode 100644 09_abuse/nouns.txt diff --git a/09_abuse/adjectives.txt b/09_abuse/adjectives.txt new file mode 100644 index 000000000..637d9944d --- /dev/null +++ b/09_abuse/adjectives.txt @@ -0,0 +1 @@ +bankrupt base caterwauling corrupt cullionly detestable dishonest false filthsome filthy foolish foul gross heedless indistinguishable infected insatiate irksome lascivious lecherous loathsome lubbery old peevish rascaly rotten ruinous scurilous scurvy slanderous sodden-witted thin-faced toad-spotted unmannered vile wall-eyed diff --git a/09_abuse/nouns.txt b/09_abuse/nouns.txt new file mode 100644 index 000000000..8bf7d6884 --- /dev/null +++ b/09_abuse/nouns.txt @@ -0,0 +1 @@ +Judas Satan ape ass barbermonger beggar block boy braggart butt carbuncle coward coxcomb cur dandy degenerate fiend fishmonger fool gull harpy jack jolthead knave liar lunatic maw milksop minion ratcatcher recreant rogue scold slave swine traitor varlet villain worm From 9ce2fe036ed7256af648374031b6af61e1642009 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 1 Sep 2021 21:46:38 +0900 Subject: [PATCH 44/98] ABUSE: Amended to use text from files. --- 09_abuse/abuse.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/09_abuse/abuse.py b/09_abuse/abuse.py index 373ebba5e..3e3675e32 100755 --- a/09_abuse/abuse.py +++ b/09_abuse/abuse.py @@ -8,8 +8,7 @@ import argparse import random - -# import textwrap as tw +import os # -------------------------------------------------- @@ -52,6 +51,16 @@ def get_args(): return args +def read_file(filename: str) -> str: + """ Read a file and return its contents in a list, raise FileNotFoundError + if there's an issue """ + if os.path.isfile(filename): + with open(filename, 'rt', encoding='utf-8') as f: + return f.read().split() + else: + raise FileNotFoundError(f"{filename} does not exist.") + + # -------------------------------------------------- def main(): """ Main prog """ @@ -59,21 +68,12 @@ def main(): args = get_args() random.seed(args.seed) - adjectives = """ - bankrupt base caterwauling corrupt cullionly detestable dishonest false - filthsome filthy foolish foul gross heedless indistinguishable infected - insatiate irksome lascivious lecherous loathsome lubbery old peevish - rascaly rotten ruinous scurilous scurvy slanderous sodden-witted thin-faced - toad-spotted unmannered vile wall-eyed""".split() + adjectives = read_file('./adjectives.txt') - nouns = """ - Judas Satan ape ass barbermonger beggar block boy braggart butt carbuncle - coward coxcomb cur dandy degenerate fiend fishmonger fool gull harpy jack - jolthead knave liar lunatic maw milksop minion ratcatcher recreant rogue - scold slave swine traitor varlet villain worm""".split() + nouns = read_file('./nouns.txt') for _ in range(args.number): - # Using random.sample (rather than random.choices) as the choices are unique + # Using random.sample as the choices are unique chosen_adjs = ', '.join(random.sample(adjectives, args.adjectives)) print(f"You {chosen_adjs} {random.choice(nouns)}!") From 15d0a3f02725f8cf36769d36874d699578187713 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 2 Sep 2021 19:26:59 +0900 Subject: [PATCH 45/98] Amended .gitignore to allow telephone.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a5592e823..0c05aeee0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ venv/ # 07_gashlycrumb/gashlycrumb.py # 08_apples_and_bananas/apples.py # 09_abuse/abuse.py -10_telephone/telephone.py +# 10_telephone/telephone.py 11_bottles_of_beer/bottles.py 12_ransom/ransom.py 13_twelve_days/twelve_days.py From 0423df991f0b57850f7a517679cb8af7bd1b99fc Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 2 Sep 2021 19:28:41 +0900 Subject: [PATCH 46/98] TELEPHONE: My effort using enumerate - but different answers from book. --- 10_telephone/telephone.py | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100755 10_telephone/telephone.py diff --git a/10_telephone/telephone.py b/10_telephone/telephone.py new file mode 100755 index 000000000..b30c7fd9a --- /dev/null +++ b/10_telephone/telephone.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-09-02 +Purpose: A game of telephone that randomly mutates text +""" + +import argparse +import os +import random +import string + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Telephone', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='text', + help='Input text or file') + + parser.add_argument('-s', + '--seed', + help='Random Seed', + metavar='seed', + type=int, + default=None) + + parser.add_argument('-m', + '--mutations', + help='Percent mutations', + metavar='mutations', + type=float, + default=0.1) + + args = parser.parse_args() + + # Handle out of bounds error for mutations + if not 0 <= args.mutations <= 1: + parser.error(f'--mutations "{args.mutations}" must be between 0 and 1') + + # Handle if text is a filename + if os.path.isfile(args.text): + with open(args.text, "rt", encoding="utf-8") as f: + args.text = f.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + text = args.text + random.seed(args.seed) + num_mutations = round(len(text) * args.mutations) + + # Get all letters and punctuation for mutation + alpha = ''.join(sorted(string.ascii_letters + string.punctuation)) + + # Choose indices to change in text + indexes = random.sample(range(len(text)), num_mutations) + + new_text = '' + # Below works, but has different results from book version - why? + for idx, char in enumerate(text): + if idx in indexes: + # Remove char from alpha so it cannot be put back into new string + new_text += random.choice(alpha.replace(char, '')) + else: + new_text += char + + # BOOK ANSWER + # does the process far quicker than my one (10 times!) + # new_text = text + # for i in indexes: + # new_char = random.choice(alpha.replace(new_text[i], '')) + # new_text = new_text[:i] + new_char + new_text[i + 1:] + + # Extra space before 2nd ':' to line up with first one. + print(f'You said: "{text}"\nI heard : "{new_text}"') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 637630ed603928d355c575734b3953afff3e9829 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 2 Sep 2021 20:57:31 +0900 Subject: [PATCH 47/98] TELEPHONE: Added list method to solving problem. --- 10_telephone/telephone.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/10_telephone/telephone.py b/10_telephone/telephone.py index b30c7fd9a..84fd7eaeb 100755 --- a/10_telephone/telephone.py +++ b/10_telephone/telephone.py @@ -75,13 +75,22 @@ def main(): else: new_text += char - # BOOK ANSWER - # does the process far quicker than my one (10 times!) + # # BOOK ANSWER + # # does the process far quicker than my one (10 times!) # new_text = text # for i in indexes: + # # Remove char from alpha so it cannot be put back into new string # new_char = random.choice(alpha.replace(new_text[i], '')) # new_text = new_text[:i] + new_char + new_text[i + 1:] + # # BOOK ANSWER - LIST APPROACH + # + # new_text = list(text) + # for i in indexes: + # new_text = random.choice(alpha.replace(new_text[i], '')) + # + # new_text = ''.join(new_text) + # Extra space before 2nd ':' to line up with first one. print(f'You said: "{text}"\nI heard : "{new_text}"') From 0367fb899b5c7c530ddcb7ed3d75940b0a2c1a53 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 3 Sep 2021 09:36:20 +0900 Subject: [PATCH 48/98] TELEPHONE: Using list method, added -o option to output to a file. --- 10_telephone/telephone.py | 44 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/10_telephone/telephone.py b/10_telephone/telephone.py index 84fd7eaeb..39ecc1eee 100755 --- a/10_telephone/telephone.py +++ b/10_telephone/telephone.py @@ -9,6 +9,7 @@ import os import random import string +# import sys # -------------------------------------------------- @@ -37,6 +38,13 @@ def get_args(): type=float, default=0.1) + parser.add_argument('-o', + '--output', + help='Output filename', + metavar='str', + type=str, + default='') + args = parser.parse_args() # Handle out of bounds error for mutations @@ -66,16 +74,16 @@ def main(): # Choose indices to change in text indexes = random.sample(range(len(text)), num_mutations) - new_text = '' - # Below works, but has different results from book version - why? - for idx, char in enumerate(text): - if idx in indexes: - # Remove char from alpha so it cannot be put back into new string - new_text += random.choice(alpha.replace(char, '')) - else: - new_text += char + # new_text = '' + # # Below works, but has different results from book version - why? + # for idx, char in enumerate(text): + # if idx in indexes: + # # Remove char from alpha so it cannot be put back into new string + # new_text += random.choice(alpha.replace(char, '')) + # else: + # new_text += char - # # BOOK ANSWER + # # # BOOK ANSWER # # does the process far quicker than my one (10 times!) # new_text = text # for i in indexes: @@ -84,15 +92,21 @@ def main(): # new_text = new_text[:i] + new_char + new_text[i + 1:] # # BOOK ANSWER - LIST APPROACH - # - # new_text = list(text) - # for i in indexes: - # new_text = random.choice(alpha.replace(new_text[i], '')) # - # new_text = ''.join(new_text) + new_text = list(text) + for i in indexes: + new_text[i] = random.choice(alpha.replace(new_text[i], '')) + + new_text = ''.join(new_text) # Extra space before 2nd ':' to line up with first one. - print(f'You said: "{text}"\nI heard : "{new_text}"') + message = f'You said: "{text}"\nI heard : "{new_text}"' + + if args.output: + with open(args.output, 'wt', encoding='utf-8') as outfile: + outfile.write(message + '\n') + else: + print(message) # -------------------------------------------------- From b4b827de2a3d7a0243d15a76c387ba314ba92163 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 3 Sep 2021 18:18:00 +0900 Subject: [PATCH 49/98] TELE_TEST: Added tests for output file and char flag. --- 10_telephone/test.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/10_telephone/test.py b/10_telephone/test.py index ea263af1b..47925382a 100755 --- a/10_telephone/test.py +++ b/10_telephone/test.py @@ -116,8 +116,46 @@ def test_fox_file_s2_m6(): assert out.rstrip() == f'You said: "{txt}"\nI heard : "{expected}"' +# -------------------------------------------------- +def test_text_outfile(): + """Test STDIN/outfile""" + + out_file = random_string() + if os.path.isfile(out_file): + os.remove(out_file) + + try: + rv, out = getstatusoutput(f'{prg} {out_flag()} {out_file} "foo bar baz"') + assert rv == 0 + # assert out.strip() == '' + assert os.path.isfile(out_file) + finally: + if os.path.isfile(out_file): + os.remove(out_file) + + +# -------------------------------------------------- +def test_for_char_flag(): + """test""" + + txt = open(now).read().rstrip() + rv, out = getstatusoutput(f'{prg} -s 2 -m .4 -c "{txt}"') + assert rv == 0 + expected = """ + Nob ic HheDriqe KorkallbgHojJmenxtS clleGtCutheuaidypyHthe laHty. + """.strip() + assert out.rstrip() == f'You said: "{txt}"\nI heard : "{expected}"' + + # -------------------------------------------------- def random_string(): """generate a random filename""" return ''.join(random.choices(string.ascii_lowercase + string.digits, k=5)) + + +# -------------------------------------------------- +def out_flag(): + """Either -o or --output""" + + return '-o' if random.randint(0, 1) else '--output' From 9dd440a53c97667a933b318f5134c44ae8351421 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 3 Sep 2021 18:18:38 +0900 Subject: [PATCH 50/98] TELEPHONE: Added and handled -c flag for only characters in replacement. --- 10_telephone/telephone.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/10_telephone/telephone.py b/10_telephone/telephone.py index 39ecc1eee..08f479c17 100755 --- a/10_telephone/telephone.py +++ b/10_telephone/telephone.py @@ -45,6 +45,11 @@ def get_args(): type=str, default='') + parser.add_argument('-c', + '--characters', + help='Only use characters to replace', + action='store_true') + args = parser.parse_args() # Handle out of bounds error for mutations @@ -68,8 +73,11 @@ def main(): random.seed(args.seed) num_mutations = round(len(text) * args.mutations) - # Get all letters and punctuation for mutation - alpha = ''.join(sorted(string.ascii_letters + string.punctuation)) + # Get all letters (and punctuation) for mutation + if args.characters: + alpha = string.ascii_letters + else: + alpha = ''.join(sorted(string.ascii_letters + string.punctuation)) # Choose indices to change in text indexes = random.sample(range(len(text)), num_mutations) From 29a343d6308347ca69b1db025ef47f91999eed03 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 3 Sep 2021 19:37:35 +0900 Subject: [PATCH 51/98] Amended .gitignore to allow bottles.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0c05aeee0..ea33258c3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ venv/ # 08_apples_and_bananas/apples.py # 09_abuse/abuse.py # 10_telephone/telephone.py -11_bottles_of_beer/bottles.py +# 11_bottles_of_beer/bottles.py 12_ransom/ransom.py 13_twelve_days/twelve_days.py 14_rhymer/rhymer.py From b528936aad65d7024e8b0dbec3c1c01683feb660 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 3 Sep 2021 19:38:28 +0900 Subject: [PATCH 52/98] BOTTLES: First effort using textwrap.dedent to preserve formatting. --- 11_bottles_of_beer/bottles.py | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 11_bottles_of_beer/bottles.py diff --git a/11_bottles_of_beer/bottles.py b/11_bottles_of_beer/bottles.py new file mode 100755 index 000000000..f17c25b5d --- /dev/null +++ b/11_bottles_of_beer/bottles.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +Author : wrjt +Date : 2021-09-03 +Purpose: To generate "bottle of beer" song dpeneding on the value of n +(default: 10) +""" + +import argparse +import textwrap as tw + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Bottles of beer song', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-n', + '--num', + help='How many bottles', + metavar='number', + type=int, + default=10) + + args = parser.parse_args() + + # Handle out of bounds error for number of bottles + if args.num < 1: + parser.error(f'--num "{args.num}" must be greater than 0') + + return args + + +# -------------------------------------------------- +def verse(bottle: int) -> str: + """ Sing a verse """ + if bottle == 2: + verse = tw.dedent("""\ + 2 bottles of beer on the wall, + 2 bottles of beer, + Take one down, pass it around, + 1 bottle of beer on the wall! + """) + elif bottle == 1: + verse = tw.dedent("""\ + 1 bottle of beer on the wall, + 1 bottle of beer, + Take one down, pass it around, + No more bottles of beer on the wall!""") + else: + verse = tw.dedent(f"""\ + {bottle} bottles of beer on the wall, + {bottle} bottles of beer, + Take one down, pass it around, + {bottle - 1} bottles of beer on the wall! + """) + + return verse + + +# -------------------------------------------------- +def main(): + """ Main Prog """ + + args = get_args() + num_bottles = args.num + + for i in range(num_bottles, 0, -1): + print(verse(i)) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 6b2548664e6cf7314f22766b7aa80a25bf9d3495 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 3 Sep 2021 20:37:43 +0900 Subject: [PATCH 53/98] BOTTLES: Slimlined verse function and added unit test for it. --- 11_bottles_of_beer/bottles.py | 84 ++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/11_bottles_of_beer/bottles.py b/11_bottles_of_beer/bottles.py index f17c25b5d..5900da763 100755 --- a/11_bottles_of_beer/bottles.py +++ b/11_bottles_of_beer/bottles.py @@ -7,7 +7,7 @@ """ import argparse -import textwrap as tw +# import textwrap as tw # -------------------------------------------------- @@ -35,30 +35,42 @@ def get_args(): # -------------------------------------------------- -def verse(bottle: int) -> str: +# def verse(bottle: int) -> str: +# """ Sing a verse """ +# if bottle == 2: +# verse = tw.dedent("""\ +# 2 bottles of beer on the wall, +# 2 bottles of beer, +# Take one down, pass it around, +# 1 bottle of beer on the wall! +# """) +# elif bottle == 1: +# verse = tw.dedent("""\ +# 1 bottle of beer on the wall, +# 1 bottle of beer, +# Take one down, pass it around, +# No more bottles of beer on the wall!""") +# else: +# verse = tw.dedent(f"""\ +# {bottle} bottles of beer on the wall, +# {bottle} bottles of beer, +# Take one down, pass it around, +# {bottle - 1} bottles of beer on the wall! +# """) + +# return verse + + +def verse(bottle): """ Sing a verse """ - if bottle == 2: - verse = tw.dedent("""\ - 2 bottles of beer on the wall, - 2 bottles of beer, - Take one down, pass it around, - 1 bottle of beer on the wall! - """) - elif bottle == 1: - verse = tw.dedent("""\ - 1 bottle of beer on the wall, - 1 bottle of beer, - Take one down, pass it around, - No more bottles of beer on the wall!""") - else: - verse = tw.dedent(f"""\ - {bottle} bottles of beer on the wall, - {bottle} bottles of beer, - Take one down, pass it around, - {bottle - 1} bottles of beer on the wall! - """) - - return verse + + current_bottle = '1 bottle' if bottle == 1 else f'{bottle} bottles' + next_bottle = 'No more bottles' if bottle == 1 else f'{bottle - 1} bottle' if bottle == 2 else f'{bottle - 1} bottles' + + return '\n'.join([ + f'{current_bottle} of beer on the wall,', f'{current_bottle} of beer,', + 'Take one down, pass it around,', f'{next_bottle} of beer on the wall!' + ]) # -------------------------------------------------- @@ -66,10 +78,28 @@ def main(): """ Main Prog """ args = get_args() - num_bottles = args.num - for i in range(num_bottles, 0, -1): - print(verse(i)) + # for i in range(args.num, 0, -1): + # print(verse(i)) + + print('\n\n'.join(map(verse, range(args.num, 0, -1)))) + + +def test_verse(): + """ Test verse """ + + last_verse = verse(1) + assert last_verse == '\n'.join([ + '1 bottle of beer on the wall,', '1 bottle of beer,', + 'Take one down, pass it around,', + 'No more bottles of beer on the wall!' + ]) + + two_bottles = verse(2) + assert two_bottles == '\n'.join([ + '2 bottles of beer on the wall,', '2 bottles of beer,', + 'Take one down, pass it around,', '1 bottle of beer on the wall!' + ]) # -------------------------------------------------- From 75e28b2c72ee98a11522cd6b8dc139fec35911d4 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 10:53:15 +0900 Subject: [PATCH 54/98] BOTTLES: Added alternative way to print verses. --- 11_bottles_of_beer/bottles.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/11_bottles_of_beer/bottles.py b/11_bottles_of_beer/bottles.py index 5900da763..f26a0de34 100755 --- a/11_bottles_of_beer/bottles.py +++ b/11_bottles_of_beer/bottles.py @@ -2,7 +2,7 @@ """ Author : wrjt Date : 2021-09-03 -Purpose: To generate "bottle of beer" song dpeneding on the value of n +Purpose: To generate "bottle of beer" song depending on the value of n (default: 10) """ @@ -68,8 +68,10 @@ def verse(bottle): next_bottle = 'No more bottles' if bottle == 1 else f'{bottle - 1} bottle' if bottle == 2 else f'{bottle - 1} bottles' return '\n'.join([ - f'{current_bottle} of beer on the wall,', f'{current_bottle} of beer,', - 'Take one down, pass it around,', f'{next_bottle} of beer on the wall!' + f'{current_bottle} of beer on the wall,', + f'{current_bottle} of beer,', + f'Take one down, pass it around,', + f'{next_bottle} of beer on the wall!' ]) @@ -84,6 +86,10 @@ def main(): print('\n\n'.join(map(verse, range(args.num, 0, -1)))) + # # Alternative way + # for n in range(args.num, 0, -1): + # print(verse(n), end='\n' * (2 if n > 1 else 1)) + def test_verse(): """ Test verse """ From 56049673e72cc90381605bd4e1d656e3d0852420 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 12:05:30 +0900 Subject: [PATCH 55/98] Amended .gitignore to allow ransom.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ea33258c3..f7a91815a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ venv/ # 09_abuse/abuse.py # 10_telephone/telephone.py # 11_bottles_of_beer/bottles.py -12_ransom/ransom.py +# 12_ransom/ransom.py 13_twelve_days/twelve_days.py 14_rhymer/rhymer.py 15_kentucky_friar/friar.py From 07fe58321fb27f07f9900bd5e5b5201e8e86c56b Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 12:06:25 +0900 Subject: [PATCH 56/98] RANSOM: First effort, got fails in test because of how I did choose function. --- 12_ransom/ransom.py | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 12_ransom/ransom.py diff --git a/12_ransom/ransom.py b/12_ransom/ransom.py new file mode 100755 index 000000000..6a871f5a2 --- /dev/null +++ b/12_ransom/ransom.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Title : Ransom Note +Author : wrjt +Date : 2021-09-04 +Purpose: Encode some text with random capitaization +""" + +import argparse +import os +import random + + +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Ransom Note', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='text', + help='Input text or file') + + parser.add_argument('-s', + '--seed', + help='Random Seed', + metavar='seed', + type=int, + default=None) + + args = parser.parse_args() + + # Handle if text is a filename + if os.path.isfile(args.text): + with open(args.text, "rt", encoding="utf-8") as f: + args.text = f.read().rstrip() + + return args + + +def main(): + """ Main Prog """ + + args = get_args() + random.seed(args.seed) + + ransom_note = [choose(char) for char in args.text] + # ransom_note = map(choose, args.text) + + print(''.join(ransom_note)) + + +def choose(char: str) -> str: + """ Change case of a character randomly """ + + # # My version returned different results than his - why? + # return random.choice([char.upper(), char.lower()]) + return char.upper() if random.choice([0, 1]) else char.lower() + + +def test_choose(): + """ Test choose() """ + + state = random.getstate() + random.seed(1) + assert choose('a') == 'a' + assert choose('b') == 'b' + assert choose('c') == 'C' + assert choose('d') == 'd' + random.setstate(state) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 1072486483cd6ddd8434789d7a8b9a20dce40c30 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 15:21:18 +0900 Subject: [PATCH 57/98] Amended .gitignore to allow twelve_days.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f7a91815a..0b4a262db 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ venv/ # 10_telephone/telephone.py # 11_bottles_of_beer/bottles.py # 12_ransom/ransom.py -13_twelve_days/twelve_days.py +# 13_twelve_days/twelve_days.py 14_rhymer/rhymer.py 15_kentucky_friar/friar.py 16_scrambler/scrambler.py From 3aec25f3f96f5a4077fa9041535170e3924a4c3d Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 15:23:11 +0900 Subject: [PATCH 58/98] 12_DAYS: First effort, passes all tests. --- 13_twelve_days/twelve_days.py | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100755 13_twelve_days/twelve_days.py diff --git a/13_twelve_days/twelve_days.py b/13_twelve_days/twelve_days.py new file mode 100755 index 000000000..2e6a2c957 --- /dev/null +++ b/13_twelve_days/twelve_days.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +Title : Twelve Days Of Christmas +Author : wrjt +Date : 2021-09-04 +Purpose: Rock the Casbah +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Twelve Days Of Christmas', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-n', + '--num', + help='Number of days to sing', + metavar='days', + type=int, + default=12) + + parser.add_argument('-o', + '--output', + help='Output filename', + metavar='str', + type=str, + default='') + + args = parser.parse_args() + + # Handle out of bounds error for number of days + if not 1 <= args.num <= 12: + parser.error(f'--num "{args.num}" must be between 1 and 12') + + return args + + +# -------------------------------------------------- +def main(): + """ Main Prog """ + + args = get_args() + + verses = '\n\n'.join(map(verse, range(1, args.num + 1))) + + if args.output: + with open(args.output, 'wt', encoding='utf-8') as outfile: + outfile.write(verses + '\n') + else: + print(verses) + + +def verse(day: int) -> str: + """ Return a verse of 12 Days of Xmas """ + + ordinals = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', + 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth'] + + gifts = ['A partridge in a pear tree.', + 'Two turtle doves,', + 'Three French hens,', + 'Four calling birds,', + 'Five gold rings,', + 'Six geese a laying,', + 'Seven swans a swimming,', + 'Eight maids a milking,', + 'Nine ladies dancing,', + 'Ten lords a leaping,', + 'Eleven pipers piping,', + 'Twelve drummers drumming,'] + + lines = [f"On the {ordinals[day - 1]} day of Christmas,", + "My true love gave to me,"] + + for gift in reversed(gifts[1:day]): + lines.append(gift) + + if day > 1: + lines.append("And " + gifts[0].lower()) + else: + lines.append(gifts[0]) + + return '\n'.join(lines) + + +def test_verse(): + """ Test verse() """ + + assert verse(1) == '\n'.join([ + 'On the first day of Christmas,', 'My true love gave to me,', + 'A partridge in a pear tree.' + ]) + + assert verse(2) == '\n'.join([ + 'On the second day of Christmas,', 'My true love gave to me,', + 'Two turtle doves,', 'And a partridge in a pear tree.' + ]) + + assert verse(3) == '\n'.join([ + 'On the third day of Christmas,', 'My true love gave to me,', + 'Three French hens,', 'Two turtle doves,', + 'And a partridge in a pear tree.' + ]) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 76c0be19993b8b907508ef51fa2092751914550a Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 15:42:23 +0900 Subject: [PATCH 59/98] 12_DAYS: Added book version differences. --- 13_twelve_days/twelve_days.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/13_twelve_days/twelve_days.py b/13_twelve_days/twelve_days.py index 2e6a2c957..72efce9a9 100755 --- a/13_twelve_days/twelve_days.py +++ b/13_twelve_days/twelve_days.py @@ -34,7 +34,7 @@ def get_args(): args = parser.parse_args() # Handle out of bounds error for number of days - if not 1 <= args.num <= 12: + if args.num not in range(1, 13): parser.error(f'--num "{args.num}" must be between 1 and 12') return args @@ -85,6 +85,12 @@ def verse(day: int) -> str: else: lines.append(gifts[0]) + # # Book version + # lines.extend(reversed(gifts[:day])) + + # if day > 1: + # lines[-1] = 'And '+ lines[-1].lower() + return '\n'.join(lines) From d70f9d0fe400178375162bfc7c03bf5ecb8757df Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 16:05:04 +0900 Subject: [PATCH 60/98] 12_DAYS: Used list comprehension instead of map. --- 13_twelve_days/twelve_days.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/13_twelve_days/twelve_days.py b/13_twelve_days/twelve_days.py index 72efce9a9..2a9aff71c 100755 --- a/13_twelve_days/twelve_days.py +++ b/13_twelve_days/twelve_days.py @@ -46,7 +46,8 @@ def main(): args = get_args() - verses = '\n\n'.join(map(verse, range(1, args.num + 1))) + # verses = '\n\n'.join(map(verse, range(1, args.num + 1))) + verses = '\n\n'.join([verse(n) for n in range(1, args.num + 1)]) if args.output: with open(args.output, 'wt', encoding='utf-8') as outfile: @@ -89,7 +90,7 @@ def verse(day: int) -> str: # lines.extend(reversed(gifts[:day])) # if day > 1: - # lines[-1] = 'And '+ lines[-1].lower() + # lines[-1] = 'And '+ lines[-1].lower() return '\n'.join(lines) From 9b40cb22785bf44113a17ec5badb38312884343b Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 17:15:16 +0900 Subject: [PATCH 61/98] Added already existing file handling for howler, 12 days and telephone scripts. --- 05_howler/howler.py | 10 +++++++- 10_telephone/telephone.py | 6 +++++ 13_twelve_days/twelve_days.py | 45 ++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/05_howler/howler.py b/05_howler/howler.py index aeb3c4086..c859dd12f 100755 --- a/05_howler/howler.py +++ b/05_howler/howler.py @@ -27,7 +27,15 @@ def get_args(): type=str, default='') - return parser.parse_args() + args = parser.parse_args() + + # Handle already existing file + if os.path.isfile(args.outfile): + parser.error( + f'--outfile "{args.outfile}" file already exists: please choose another name.' + ) + + return args # -------------------------------------------------- diff --git a/10_telephone/telephone.py b/10_telephone/telephone.py index 08f479c17..3fe42f67d 100755 --- a/10_telephone/telephone.py +++ b/10_telephone/telephone.py @@ -61,6 +61,12 @@ def get_args(): with open(args.text, "rt", encoding="utf-8") as f: args.text = f.read().rstrip() + # Handle already existing file + if os.path.isfile(args.output): + parser.error( + f'--output "{args.output}" file already exists: please choose another name.' + ) + return args diff --git a/13_twelve_days/twelve_days.py b/13_twelve_days/twelve_days.py index 2a9aff71c..91da23977 100755 --- a/13_twelve_days/twelve_days.py +++ b/13_twelve_days/twelve_days.py @@ -7,6 +7,7 @@ """ import argparse +import os # -------------------------------------------------- @@ -37,6 +38,12 @@ def get_args(): if args.num not in range(1, 13): parser.error(f'--num "{args.num}" must be between 1 and 12') + # Handle already existing file + if os.path.isfile(args.output): + parser.error( + f'--output "{args.output}" file already exists: please choose another name.' + ) + return args @@ -59,24 +66,24 @@ def main(): def verse(day: int) -> str: """ Return a verse of 12 Days of Xmas """ - ordinals = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', - 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth'] - - gifts = ['A partridge in a pear tree.', - 'Two turtle doves,', - 'Three French hens,', - 'Four calling birds,', - 'Five gold rings,', - 'Six geese a laying,', - 'Seven swans a swimming,', - 'Eight maids a milking,', - 'Nine ladies dancing,', - 'Ten lords a leaping,', - 'Eleven pipers piping,', - 'Twelve drummers drumming,'] - - lines = [f"On the {ordinals[day - 1]} day of Christmas,", - "My true love gave to me,"] + ordinals = [ + 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', + 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth' + ] + + gifts = [ + 'A partridge in a pear tree.', 'Two turtle doves,', + 'Three French hens,', 'Four calling birds,', 'Five gold rings,', + 'Six geese a laying,', 'Seven swans a swimming,', + 'Eight maids a milking,', 'Nine ladies dancing,', + 'Ten lords a leaping,', 'Eleven pipers piping,', + 'Twelve drummers drumming,' + ] + + lines = [ + f"On the {ordinals[day - 1]} day of Christmas,", + "My true love gave to me," + ] for gift in reversed(gifts[1:day]): lines.append(gift) @@ -90,7 +97,7 @@ def verse(day: int) -> str: # lines.extend(reversed(gifts[:day])) # if day > 1: - # lines[-1] = 'And '+ lines[-1].lower() + # lines[-1] = 'And '+ lines[-1].lower() return '\n'.join(lines) From 26ea1e85daea358915efd5a4bb17626addfa203b Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 22:07:07 +0900 Subject: [PATCH 62/98] Amended .gitignore to allow rhymer.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0b4a262db..5f7b5aada 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ venv/ # 11_bottles_of_beer/bottles.py # 12_ransom/ransom.py # 13_twelve_days/twelve_days.py -14_rhymer/rhymer.py +# 14_rhymer/rhymer.py 15_kentucky_friar/friar.py 16_scrambler/scrambler.py 17_mad_libs/mad.py From 7929e82f559d8da96a99fc6649732b887d6c96cc Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 22:07:45 +0900 Subject: [PATCH 63/98] RHYME: First effort using for loop. --- 14_rhymer/rhymer.py | 93 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100755 14_rhymer/rhymer.py diff --git a/14_rhymer/rhymer.py b/14_rhymer/rhymer.py new file mode 100755 index 000000000..031c2f1e3 --- /dev/null +++ b/14_rhymer/rhymer.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +""" +Title : Rhymer +Author : wrjt +Date : 2021-09-04 +Purpose: Find rhyming words using regexes +""" + +import argparse +import re +import string + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Make rhyming "words"', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('word', metavar='word', help='A word to rhyme') + + args = parser.parse_args() + + # if len(args.word) != 1: + # parser.error("Please only enter one word.") + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + + consonants = list('bcdfghjklmnpqrstvwxyz') + clusters = """\ + bl br ch cl cr dr fl fr gl gr pl pr sc sh sk sl sm sn sp st sw th + tr tw thw wh wr sch scr shr sph spl spr squ str thr""".split() + prefixes = sorted(consonants + clusters) + + remove_me, stem = stemmer(args.word) + + if stem: + output = [] + for p in prefixes: + if p == remove_me: + continue + else: + output.append(p + stem) + output = '\n'.join(output) + else: + output = f'Cannot rhyme "{args.word}"' + + print(output) + + +def stemmer(word: str) -> tuple: + """ Return leading consonants (if any), and 'stem' of the word """ + + letters, vowels = string.ascii_lowercase, 'aeiou' + consonants = ''.join([c for c in letters if c not in vowels]) + + pattern = f'([{consonants}]+)?([{vowels}])(.*)' + + word = word.lower() + match = re.match(pattern, word) + # match = re.match(pattern, word, re.IGNORECASE) + + if match: + p1 = match.group(1) or '' + p2 = match.group(2) or '' + p3 = match.group(3) or '' + return (p1, p2 + p3) + else: + return (word, '') + + +def test_stemmer(): + """ Test stemmer() """ + assert stemmer('') == ('', '') + assert stemmer('cake') == ('c', 'ake') + assert stemmer('chair') == ('ch', 'air') + assert stemmer('APPLE') == ('', 'apple') + assert stemmer('RDNZL') == ('rdnzl', '') + assert stemmer('123') == ('123', '') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 9d5cc294ab855e0c5e37dbd7b57786f2efaa0d61 Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 4 Sep 2021 22:14:56 +0900 Subject: [PATCH 64/98] RHYME: Changed loop to list comprehension. --- 14_rhymer/rhymer.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/14_rhymer/rhymer.py b/14_rhymer/rhymer.py index 031c2f1e3..457631af2 100755 --- a/14_rhymer/rhymer.py +++ b/14_rhymer/rhymer.py @@ -44,13 +44,7 @@ def main(): remove_me, stem = stemmer(args.word) if stem: - output = [] - for p in prefixes: - if p == remove_me: - continue - else: - output.append(p + stem) - output = '\n'.join(output) + output = '\n'.join([p + stem for p in prefixes if p != remove_me]) else: output = f'Cannot rhyme "{args.word}"' From 3bfa9e695c318c9bd9fb41b64efa5f3cfa0cba9e Mon Sep 17 00:00:00 2001 From: Franco Date: Sun, 5 Sep 2021 11:17:34 +0900 Subject: [PATCH 65/98] RHYME: Used re.compile & findall for stemmer. --- 14_rhymer/rhymer.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/14_rhymer/rhymer.py b/14_rhymer/rhymer.py index 457631af2..0f68f7578 100755 --- a/14_rhymer/rhymer.py +++ b/14_rhymer/rhymer.py @@ -56,21 +56,37 @@ def stemmer(word: str) -> tuple: letters, vowels = string.ascii_lowercase, 'aeiou' consonants = ''.join([c for c in letters if c not in vowels]) + word = word.lower() - pattern = f'([{consonants}]+)?([{vowels}])(.*)' + # Alternative using re.compile and findall (which returns a list) + pattern_regex = re.compile( + rf'''( + ([{consonants}]+)? # Capture one or more (optional) + ([{vowels}]+) # Capture at least one vowel + (.*) # Capture zero or more of anything else + )''', re.VERBOSE) - word = word.lower() - match = re.match(pattern, word) - # match = re.match(pattern, word, re.IGNORECASE) + match = pattern_regex.findall(word) if match: - p1 = match.group(1) or '' - p2 = match.group(2) or '' - p3 = match.group(3) or '' + p1 = match[0][1] or '' + p2 = match[0][2] or '' + p3 = match[0][3] or '' return (p1, p2 + p3) else: return (word, '') + # pattern = f'([{consonants}]+)?([{vowels}])(.*)' + # match = re.match(pattern, word) + + # if match: + # p1 = match.group(1) or '' + # p2 = match.group(2) or '' + # p3 = match.group(3) or '' + # return (p1, p2 + p3) + # else: + # return (word, '') + def test_stemmer(): """ Test stemmer() """ From 2729a36141e8373cd58d4421917590fdf95d94e0 Mon Sep 17 00:00:00 2001 From: Franco Date: Sun, 5 Sep 2021 12:05:58 +0900 Subject: [PATCH 66/98] RHYME: Added a filter version of acquiring consonants. --- 14_rhymer/rhymer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/14_rhymer/rhymer.py b/14_rhymer/rhymer.py index 0f68f7578..c3d92d556 100755 --- a/14_rhymer/rhymer.py +++ b/14_rhymer/rhymer.py @@ -56,6 +56,7 @@ def stemmer(word: str) -> tuple: letters, vowels = string.ascii_lowercase, 'aeiou' consonants = ''.join([c for c in letters if c not in vowels]) + # consonants = ''.join(filter(lambda c: c not in vowels, letters)) word = word.lower() # Alternative using re.compile and findall (which returns a list) From 1425d4e72b8ddf85c5a4e209be0fab5d94ac714e Mon Sep 17 00:00:00 2001 From: Franco Date: Sun, 5 Sep 2021 12:11:47 +0900 Subject: [PATCH 67/98] RHYME: Changed pattern to be formatted using () and strings for each line. --- 14_rhymer/rhymer.py | 48 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/14_rhymer/rhymer.py b/14_rhymer/rhymer.py index c3d92d556..3ba4bf8a1 100755 --- a/14_rhymer/rhymer.py +++ b/14_rhymer/rhymer.py @@ -59,35 +59,39 @@ def stemmer(word: str) -> tuple: # consonants = ''.join(filter(lambda c: c not in vowels, letters)) word = word.lower() - # Alternative using re.compile and findall (which returns a list) - pattern_regex = re.compile( - rf'''( - ([{consonants}]+)? # Capture one or more (optional) - ([{vowels}]+) # Capture at least one vowel - (.*) # Capture zero or more of anything else - )''', re.VERBOSE) + # # Alternative using re.compile and findall (which returns a list) + # pattern_regex = re.compile( + # rf'''( + # ([{consonants}]+)? # Capture one or more (optional) + # ([{vowels}]+) # Capture at least one vowel + # (.*) # Capture zero or more of anything else + # )''', re.VERBOSE) - match = pattern_regex.findall(word) - - if match: - p1 = match[0][1] or '' - p2 = match[0][2] or '' - p3 = match[0][3] or '' - return (p1, p2 + p3) - else: - return (word, '') - - # pattern = f'([{consonants}]+)?([{vowels}])(.*)' - # match = re.match(pattern, word) + # match = pattern_regex.findall(word) # if match: - # p1 = match.group(1) or '' - # p2 = match.group(2) or '' - # p3 = match.group(3) or '' + # p1 = match[0][1] or '' + # p2 = match[0][2] or '' + # p3 = match[0][3] or '' # return (p1, p2 + p3) # else: # return (word, '') + pattern = ( + f'([{consonants}]+)?' # Capture one or more (optional) + f'([{vowels}])' # Capture at least one vowel + '(.*)' # Capture zero of more of anything else + ) + match = re.match(pattern, word) + + if match: + p1 = match.group(1) or '' + p2 = match.group(2) or '' + p3 = match.group(3) or '' + return (p1, p2 + p3) + else: + return (word, '') + def test_stemmer(): """ Test stemmer() """ From 933d0bd586e61378d89bd2cc6b02cdb92b5eeb2b Mon Sep 17 00:00:00 2001 From: Franco Date: Mon, 6 Sep 2021 18:53:17 +0900 Subject: [PATCH 68/98] Amended .gitignore to allow friar.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5f7b5aada..7520c529e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ venv/ # 12_ransom/ransom.py # 13_twelve_days/twelve_days.py # 14_rhymer/rhymer.py -15_kentucky_friar/friar.py +# 15_kentucky_friar/friar.py 16_scrambler/scrambler.py 17_mad_libs/mad.py 18_gematria/gematria.py From 70ea2a982d3b1aced0fd9b401bac83cdf77e443e Mon Sep 17 00:00:00 2001 From: Franco Date: Mon, 6 Sep 2021 18:54:18 +0900 Subject: [PATCH 69/98] FRIAR: First effort wih both regex and non-regex versions. --- 15_kentucky_friar/friar.py | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100755 15_kentucky_friar/friar.py diff --git a/15_kentucky_friar/friar.py b/15_kentucky_friar/friar.py new file mode 100755 index 000000000..3508a353d --- /dev/null +++ b/15_kentucky_friar/friar.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Title : Kentucky Friar Accent +Author : wrjt +Date : 2021-09-05 +Purpose: Rock the Casbah +""" + +import argparse +import os +import re + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Southern fry text', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + args = parser.parse_args() + + # Handle if text is a filename + if os.path.isfile(args.text): + with open(args.text, "rt", encoding="utf-8") as f: + args.text = f.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """ Main Prog """ + + args = get_args() + + for line in args.text.splitlines(): + words = [fry(word) for word in re.split(r'(\W+)', line.rstrip())] + print(''.join(words)) + + +def fry(word: str) -> str: + """ 'Fry' a word """ + you_match = re.match(r'^([yY])ou$', word) + ing_match = re.search(r'(.+)ing$', word) + if you_match: + return you_match.group(1) + "'all" + elif ing_match: + # Check for vowels before 'ing', if present 'fry' it + if re.search(r'[aeoiuy]', ing_match.group(1), re.IGNORECASE): + return ing_match.group(1) + "in'" + + # # Non-regex version (passes all unit tests) + # if word.lower() == "you": + # return word[0] + "'all" + # elif word.lower().endswith('ing'): + # for c in word[:-3]: + # if c.lower() in 'aeiouy': + # return word[:-1] + "'" + + # Return word if not 'you' or ending in 'ing' (or one syllable 'ing') + return word + + +def test_fry(): + """ Test fry() """ + assert fry('you') == "y'all" + assert fry('You') == "Y'all" + assert fry('your') == "your" + assert fry('fishing') == "fishin'" + assert fry('Aching') == "Achin'" + assert fry('swing') == "swing" + assert fry('trying') == "tryin'" + assert fry('and') == "and" + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 21e9f3d8d561c78ef0ff2f6e64945eeffe6c6f5c Mon Sep 17 00:00:00 2001 From: Franco Date: Mon, 6 Sep 2021 19:14:45 +0900 Subject: [PATCH 70/98] FRIAR: Made a couple of minor adjustments based on book version. --- 15_kentucky_friar/friar.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/15_kentucky_friar/friar.py b/15_kentucky_friar/friar.py index 3508a353d..fdc1517a5 100755 --- a/15_kentucky_friar/friar.py +++ b/15_kentucky_friar/friar.py @@ -44,14 +44,15 @@ def main(): def fry(word: str) -> str: """ 'Fry' a word """ - you_match = re.match(r'^([yY])ou$', word) + you_match = re.match(r'([yY])ou$', word) ing_match = re.search(r'(.+)ing$', word) if you_match: return you_match.group(1) + "'all" elif ing_match: # Check for vowels before 'ing', if present 'fry' it - if re.search(r'[aeoiuy]', ing_match.group(1), re.IGNORECASE): - return ing_match.group(1) + "in'" + prefix = ing_match.group(1) + if re.search(r'[aeoiuy]', prefix, re.IGNORECASE): + return prefix + "in'" # # Non-regex version (passes all unit tests) # if word.lower() == "you": @@ -70,6 +71,7 @@ def test_fry(): assert fry('you') == "y'all" assert fry('You') == "Y'all" assert fry('your') == "your" + assert fry('Bayou') == "Bayou" assert fry('fishing') == "fishin'" assert fry('Aching') == "Achin'" assert fry('swing') == "swing" From 3d8b753c19cc6039f0c6e5f7ec0591b0ad3ba869 Mon Sep 17 00:00:00 2001 From: Franco Date: Mon, 6 Sep 2021 20:22:45 +0900 Subject: [PATCH 71/98] FRIAR: Added some commments to make file more explanatory. --- 15_kentucky_friar/friar.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/15_kentucky_friar/friar.py b/15_kentucky_friar/friar.py index fdc1517a5..ea38c76d2 100755 --- a/15_kentucky_friar/friar.py +++ b/15_kentucky_friar/friar.py @@ -37,14 +37,19 @@ def main(): args = get_args() + # Saves a compile every loop and is more readable + splitter = re.compile(r'(\W+)') for line in args.text.splitlines(): - words = [fry(word) for word in re.split(r'(\W+)', line.rstrip())] + words = [fry(word) for word in re.split(splitter, line.rstrip())] print(''.join(words)) def fry(word: str) -> str: """ 'Fry' a word """ + + # re.match starts at the beginning of supplied string you_match = re.match(r'([yY])ou$', word) + # re.search looks anywhere in the supplied string ing_match = re.search(r'(.+)ing$', word) if you_match: return you_match.group(1) + "'all" @@ -58,6 +63,9 @@ def fry(word: str) -> str: # if word.lower() == "you": # return word[0] + "'all" # elif word.lower().endswith('ing'): + # # Can replace for loop with: + # # if any(map(lambda c: c.lower() in 'aeiouy', word[:-3])): + # # return word[:-1] + "'" # for c in word[:-3]: # if c.lower() in 'aeiouy': # return word[:-1] + "'" @@ -71,7 +79,6 @@ def test_fry(): assert fry('you') == "y'all" assert fry('You') == "Y'all" assert fry('your') == "your" - assert fry('Bayou') == "Bayou" assert fry('fishing') == "fishin'" assert fry('Aching') == "Achin'" assert fry('swing') == "swing" From 076cfcdd191f5601ee101a5ae0524a3ed830d5d1 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 7 Sep 2021 09:28:39 +0900 Subject: [PATCH 72/98] Amended .gitignore to allow scrambler.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7520c529e..1fef8140c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ venv/ # 13_twelve_days/twelve_days.py # 14_rhymer/rhymer.py # 15_kentucky_friar/friar.py -16_scrambler/scrambler.py +# 16_scrambler/scrambler.py 17_mad_libs/mad.py 18_gematria/gematria.py 19_wod/wod.py From 6b6b9a252495d699b498060fc0fd3e04806b1e3d Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 7 Sep 2021 09:29:50 +0900 Subject: [PATCH 73/98] SCRAMBLER: First effort using a list comprehension. --- 16_scrambler/scrambler.py | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100755 16_scrambler/scrambler.py diff --git a/16_scrambler/scrambler.py b/16_scrambler/scrambler.py new file mode 100755 index 000000000..78f05a8a3 --- /dev/null +++ b/16_scrambler/scrambler.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +Title : Scrambler +Author : wrjt +Date : 2021-09-06 +Purpose: Scrambles the middle letters in words +""" + +import argparse +import os +import random +import re + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Scramble the letters or words', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', metavar='text', help='Input text or file') + + parser.add_argument('-s', + '--seed', + help='Random seed', + metavar='seed', + type=int, + default=None) + + args = parser.parse_args() + + # Handle if text is a filename + if os.path.isfile(args.text): + with open(args.text, "rt", encoding="utf-8") as f: + args.text = f.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + random.seed(args.seed) + + # Regex to capture words with apostrophes (e.g. "don't") + splitter = re.compile( + r""" + ([a-zA-Z] # Capture any letter + (?:[a-zA-Z']* # Account for any letter plus apostrophe + [a-zA-Z])?) # Followed by any letter (optional group) + """, re.VERBOSE) + + for line in args.text.splitlines(): + print(''.join([scramble(word) for word in splitter.split(line)])) + + +def scramble(word: str) -> str: + """ Scramble a word """ + + if len(word) <= 3: + return word + + first = word[0] + last = word[-1] + middle = list(word[1:-1]) + + random.shuffle(middle) + + return first + ''.join(middle) + last + + +def test_scramble(): + """ Test scramble() """ + state = random.getstate() + random.seed(1) + assert scramble("a") == "a" + assert scramble("ab") == "ab" + assert scramble("abc") == "abc" + assert scramble("abcd") == "acbd" + assert scramble("abcde") == "acbde" + assert scramble("abcdef") == "aecbdf" + assert scramble("abcde'f") == "abcd'ef" + random.setstate(state) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From f8585591a30a81e6856261f19132e7e7bb96f19d Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 7 Sep 2021 09:36:04 +0900 Subject: [PATCH 74/98] SCRAMBLER: Neatened up scramble(). --- 16_scrambler/scrambler.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/16_scrambler/scrambler.py b/16_scrambler/scrambler.py index 78f05a8a3..779875303 100755 --- a/16_scrambler/scrambler.py +++ b/16_scrambler/scrambler.py @@ -59,18 +59,14 @@ def main(): def scramble(word: str) -> str: - """ Scramble a word """ + """ For words longer than 3 chars, scramble middle letters """ - if len(word) <= 3: - return word + if len(word) > 3 and re.match(r'\w+', word): + middle = list(word[1:-1]) + random.shuffle(middle) + word = word[0] + ''.join(middle) + word[-1] - first = word[0] - last = word[-1] - middle = list(word[1:-1]) - - random.shuffle(middle) - - return first + ''.join(middle) + last + return word def test_scramble(): From 1e63e87b348a8b32d4acc12148c0210a333371cb Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 7 Sep 2021 22:53:17 +0900 Subject: [PATCH 75/98] Amended .gitignore to allow mad.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1fef8140c..82c33720d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ venv/ # 14_rhymer/rhymer.py # 15_kentucky_friar/friar.py # 16_scrambler/scrambler.py -17_mad_libs/mad.py +# 17_mad_libs/mad.py 18_gematria/gematria.py 19_wod/wod.py 20_password/password.py From cc2980f07ef0085d3442c7292923aac88fdeea49 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 7 Sep 2021 22:54:24 +0900 Subject: [PATCH 76/98] MADLIBS: First effort. --- 17_mad_libs/mad.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 17_mad_libs/mad.py diff --git a/17_mad_libs/mad.py b/17_mad_libs/mad.py new file mode 100755 index 000000000..17f6f1af6 --- /dev/null +++ b/17_mad_libs/mad.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +""" +Title : Mad Libs +Author : wrjt +Date : 2021-09-07 +Purpose: Replace parts of speech in a string with user input +""" + +import argparse +import os +import re +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Mad Libs', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', metavar='file', help='Input file') + + parser.add_argument('-i', + '--inputs', + help='Inputs (for testing)', + metavar='input', + type=str, + default='None', + nargs='*') + + args = parser.parse_args() + + # Handle file not present + if not os.path.isfile(args.file): + parser.error(f"No such file or directory: '{args.file}'") + + return args + + +# -------------------------------------------------- +def main(): + """ Main Prog """ + + args = get_args() + inputs = args.inputs + with open(args.file, 'rt', encoding='utf-8') as f: + text = f.read().rstrip() + + # Find part of speech (denoted by <>), return a tuple (placeholder, part) + part_of_speech_regex = re.compile(r'(<([^<>]+)>)') + + # Assume one line of text + matches = re.findall(part_of_speech_regex, text) + if not matches: + # Spit out an error and exit if no placeholders found in text + sys.exit(f'"{args.file}" has no placeholders.') + # Get user inputs + tmpl = 'Give me {} {}: ' + for placeholder, part in matches: + article = 'an' if part[0].lower() in 'aeiou' else 'a' + answer = inputs.pop(0) if inputs else input(tmpl.format(article, part)) + text = re.sub(placeholder, answer, text, count=1) + + print(text) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From a54bdb7b5ca3387a4ee6d540734603ce555f87c5 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 8 Sep 2021 20:43:03 +0900 Subject: [PATCH 77/98] MAD_NOREG: First effort (no regex). --- 17_mad_libs/mad_noregex.py | 106 +++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 17_mad_libs/mad_noregex.py diff --git a/17_mad_libs/mad_noregex.py b/17_mad_libs/mad_noregex.py new file mode 100755 index 000000000..7c3739296 --- /dev/null +++ b/17_mad_libs/mad_noregex.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Title : Mad Libs +Author : wrjt +Date : 2021-09-07 +Purpose: Replace parts of speech in a string with user input +""" + +import argparse +import os +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Mad Libs', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', metavar='file', help='Input file') + + parser.add_argument('-i', + '--inputs', + help='Inputs (for testing)', + metavar='input', + type=str, + default='None', + nargs='*') + + args = parser.parse_args() + + # Handle file not present + if not os.path.isfile(args.file): + parser.error(f"No such file or directory: '{args.file}'") + + return args + + +# -------------------------------------------------- +def main(): + """ Main Prog """ + + args = get_args() + inputs = args.inputs + with open(args.file, 'rt', encoding='utf-8') as f: + text = f.read().rstrip() + + if find_brackets(text) is None: + # Spit out an error and exit if no placeholders found in text + sys.exit(f'"{args.file}" has no placeholders.') + + # Get user inputs + nu_text = '' + tmpl = 'Give me {} {}: ' + + while find_brackets(text) is not None: + left, right = find_brackets(text) + article = 'an' if text[left + 1].lower() in 'aeiou' else 'a' + if inputs: + word = inputs.pop(0) + else: + word = input(tmpl.format(article, text[left + 1:right])) + nu_text += text[:left] + word + text = text[right + 1:] + + text = nu_text + text + print(text) + + +def find_brackets(text: str) -> tuple: + """ + Find position of angled brackets in a string and return as a tuple (start, end) + + ISSUES: + - Will return a value for "" which isn't what we want + (Maybe can resolve this by checking returned str matches certain + values - done outside of this function though) + - Will let mismatched brackets through (e.g. "<") + (again, resolvable outside of function) + """ + start, end = text.find('<'), text.find('>') + gap = end - start + # str.find will return -1 if substr not found + if any([gap == 1, start == -1, end == -1]): + return None + return (start, end) + + +def test_find_brackets(): + """ Test find_brackets() """ + assert find_brackets('') is None + assert find_brackets('<>') is None + assert find_brackets('') == (0, 2) + assert find_brackets('foo baz') == (4, 8) + assert find_brackets('foo bar> baz') is None + assert find_brackets('foo baz') == None + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From ddfe131eaed43a4b177176c7ba45cb58bad9ced4 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 9 Sep 2021 09:05:10 +0900 Subject: [PATCH 78/98] MAD_NOREG: Refactored, removing extra string and dealig with no brackets better. --- 17_mad_libs/mad_noregex.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/17_mad_libs/mad_noregex.py b/17_mad_libs/mad_noregex.py index 7c3739296..2d0c49cf4 100755 --- a/17_mad_libs/mad_noregex.py +++ b/17_mad_libs/mad_noregex.py @@ -47,14 +47,10 @@ def main(): with open(args.file, 'rt', encoding='utf-8') as f: text = f.read().rstrip() - if find_brackets(text) is None: - # Spit out an error and exit if no placeholders found in text - sys.exit(f'"{args.file}" has no placeholders.') - - # Get user inputs - nu_text = '' tmpl = 'Give me {} {}: ' + placeholders = False + # Get user inputs while find_brackets(text) is not None: left, right = find_brackets(text) article = 'an' if text[left + 1].lower() in 'aeiou' else 'a' @@ -62,11 +58,14 @@ def main(): word = inputs.pop(0) else: word = input(tmpl.format(article, text[left + 1:right])) - nu_text += text[:left] + word - text = text[right + 1:] + text = text[:left] + word + text[right + 1:] + placeholders = True - text = nu_text + text - print(text) + if placeholders: + print(text) + else: + # Spit out an error and exit if no placeholders found in text + sys.exit(f'"{args.file}" has no placeholders.') def find_brackets(text: str) -> tuple: From 9b30a073ff9624b5dbce5e7306ac5f3b146778b9 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 9 Sep 2021 18:30:48 +0900 Subject: [PATCH 79/98] Amended .gitignore to allow gematria.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 82c33720d..226f836b5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ venv/ # 15_kentucky_friar/friar.py # 16_scrambler/scrambler.py # 17_mad_libs/mad.py -18_gematria/gematria.py +# 18_gematria/gematria.py 19_wod/wod.py 20_password/password.py 21_tictactoe/tictactoe.py From 325abd58f9fbd6daceaa35275d83c5b33c9b7571 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 9 Sep 2021 18:31:40 +0900 Subject: [PATCH 80/98] GEMATRIA: First effort, using a beast of a list comprehension. --- 18_gematria/gematria.py | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100755 18_gematria/gematria.py diff --git a/18_gematria/gematria.py b/18_gematria/gematria.py new file mode 100755 index 000000000..da8d96ca3 --- /dev/null +++ b/18_gematria/gematria.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Title : gematria.py +Author : wrjt +Date : 2021-09-09 +Purpose: numerically encode a word using values for characters + (using ASCII values) +""" + +import argparse +import os +import re + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Gematria', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='text', + help='Input text or file') + + args = parser.parse_args() + + # Handle if text is a filename + if os.path.isfile(args.text): + with open(args.text, "rt", encoding="utf-8") as f: + args.text = f.read().rstrip() + + return args + + +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + text = args.text.splitlines() + + changed_text = [' '.join([word2num(word) for word in line.split()]) for line in text] + + print('\n'.join(changed_text)) + + +def word2num(word: str) -> str: + """ Convert a word to the sum of its ascii values """ + + # Remove non-letters and numbers + word = re.sub(re.compile(r'[^A-Za-z0-9]'), '', word) + return str(sum(ord(c) for c in word)) + + +def test_word2num(): + """ Test word2num() """ + assert word2num("a") == "97" + assert word2num("abc") == "294" + assert word2num("ab'c") == "294" + assert word2num("4a-b'c,") == "346" + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 5dee8a0fd5a93380044df51bd1aa43e10eb25b67 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 9 Sep 2021 18:54:31 +0900 Subject: [PATCH 81/98] GEMATRIA: Cleaned up main - removed unnecessary code (based on book example). --- 18_gematria/gematria.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/18_gematria/gematria.py b/18_gematria/gematria.py index da8d96ca3..498708484 100755 --- a/18_gematria/gematria.py +++ b/18_gematria/gematria.py @@ -39,11 +39,16 @@ def main(): """Make a jazz noise here""" args = get_args() - text = args.text.splitlines() - changed_text = [' '.join([word2num(word) for word in line.split()]) for line in text] + # Book version + for line in args.text.splitlines(): + print(' '.join(map(word2num, line.split()))) - print('\n'.join(changed_text)) + # text = args.text.splitlines() + + # changed_text = [' '.join([word2num(word) for word in line.split()]) for line in text] + + # print('\n'.join(changed_text)) def word2num(word: str) -> str: From 8e1b241083c826b71a17b6b2f50fc1911c2221a1 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 9 Sep 2021 21:30:47 +0900 Subject: [PATCH 82/98] GEMATRIA: Added book version for word2num. --- 18_gematria/gematria.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/18_gematria/gematria.py b/18_gematria/gematria.py index 498708484..f6895e489 100755 --- a/18_gematria/gematria.py +++ b/18_gematria/gematria.py @@ -41,19 +41,19 @@ def main(): args = get_args() # Book version - for line in args.text.splitlines(): - print(' '.join(map(word2num, line.split()))) - - # text = args.text.splitlines() - - # changed_text = [' '.join([word2num(word) for word in line.split()]) for line in text] + # for line in args.text.splitlines(): + # print(' '.join(map(word2num, line.split()))) - # print('\n'.join(changed_text)) + for line in args.text.splitlines(): + print(' '.join([word2num(word) for word in line.split()])) def word2num(word: str) -> str: """ Convert a word to the sum of its ascii values """ + # # Book version: + # return str(sum(map(ord, re.sub(r'[^A-Za-z0-9]', '', word)))) + # Remove non-letters and numbers word = re.sub(re.compile(r'[^A-Za-z0-9]'), '', word) return str(sum(ord(c) for c in word)) From 5f50602f2de778023da62a037a6885773552f4f2 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 10 Sep 2021 09:28:53 +0900 Subject: [PATCH 83/98] Amended .gitignore to allow wod.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 226f836b5..695e02740 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,6 @@ venv/ # 16_scrambler/scrambler.py # 17_mad_libs/mad.py # 18_gematria/gematria.py -19_wod/wod.py +# 19_wod/wod.py 20_password/password.py 21_tictactoe/tictactoe.py From 380cf07fe38950a35b5844f3ca4d8e55e116d560 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 10 Sep 2021 20:37:29 +0900 Subject: [PATCH 84/98] WOD: First effort, using csv.reader(). --- 19_wod/wod.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 19_wod/wod.py diff --git a/19_wod/wod.py b/19_wod/wod.py new file mode 100755 index 000000000..e29dc5735 --- /dev/null +++ b/19_wod/wod.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Title : Workout of the Day +Author : wrjt +Date : 2021-09-10 +Purpose: Create a random daily workout from choices in a csv file +""" + +import argparse +import csv +import io +import os +import random + +from tabulate import tabulate + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Create Workout Of (the) Day (WOD)', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-f', + '--file', + help='CSV input file of exercises', + metavar='FILE', + type=str, + default='./inputs/exercises.csv') + + parser.add_argument('-s', + '--seed', + help='Random seed', + metavar='seed', + type=int, + default=None) + + parser.add_argument('-n', + '--num', + help='Number of exercises', + metavar='exercises', + type=int, + default=4) + + parser.add_argument('-e', + '--easy', + help='Halve the reps', + action='store_true') + + args = parser.parse_args() + + # Handle out of bounds error for number of exercises + if args.num < 1: + parser.error(f'--num "{args.num}" must be greater than 0') + + # Handle file not present + if not os.path.isfile(args.file): + parser.error(f"No such file or directory: '{args.file}'") + + return args + + +# -------------------------------------------------- +def main(): + """ Main prog """ + + args = get_args() + random.seed(args.seed) + with open(args.file, 'rt', encoding='utf-8') as f: + exercises = read_csv(f) + + output = [] + for ex in random.sample(exercises, k=args.num): + suggested_reps = random.randint(ex[1], ex[2]) + if args.easy: + suggested_reps = suggested_reps // 2 + output.append((ex[0], suggested_reps)) + + print(tabulate(output, headers=('Exercise', 'Reps'))) + + +def read_csv(fh) -> list: + """ Read CSV formatted input, returns a list of tuples """ + reader = csv.reader(fh) + # Skip past headers + _ = next(reader) + exercises = [] + for row in reader: + name, reps = row + low, high = [int(value) for value in reps.split('-')] + exercises.append((name, low, high)) + return exercises + + +def test_read_csv(): + """ Test read_csv functions """ + # Make a fake file handle using io module + text = io.StringIO('exercise,reps\nBurpees,20-50\nSitups,40-100') + assert read_csv(text) == [('Burpees', 20, 50), ('Situps', 40, 100)] + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 0b98e937180acb5f991749cfca59411b7c2ae730 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 10 Sep 2021 20:53:00 +0900 Subject: [PATCH 85/98] WOD: Changed to unpack tuples into variables. --- 19_wod/wod.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/19_wod/wod.py b/19_wod/wod.py index e29dc5735..db7f74c1f 100755 --- a/19_wod/wod.py +++ b/19_wod/wod.py @@ -68,17 +68,17 @@ def main(): args = get_args() random.seed(args.seed) + wod = [] with open(args.file, 'rt', encoding='utf-8') as f: exercises = read_csv(f) - output = [] - for ex in random.sample(exercises, k=args.num): - suggested_reps = random.randint(ex[1], ex[2]) + for name, low, high in random.sample(exercises, k=args.num): + suggested_reps = random.randint(low, high) if args.easy: suggested_reps = suggested_reps // 2 - output.append((ex[0], suggested_reps)) + wod.append((name, suggested_reps)) - print(tabulate(output, headers=('Exercise', 'Reps'))) + print(tabulate(wod, headers=('Exercise', 'Reps'))) def read_csv(fh) -> list: From 82276c87517ddd61006c84efaf9275ff2952f011 Mon Sep 17 00:00:00 2001 From: Franco Date: Sun, 12 Sep 2021 19:29:27 +0900 Subject: [PATCH 86/98] WOD: Added code to deal with bad data, removed unit test to separate file. --- 19_wod/wod.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/19_wod/wod.py b/19_wod/wod.py index db7f74c1f..cf4c26f19 100755 --- a/19_wod/wod.py +++ b/19_wod/wod.py @@ -11,6 +11,8 @@ import io import os import random +import re +import sys from tabulate import tabulate @@ -55,6 +57,10 @@ def get_args(): if args.num < 1: parser.error(f'--num "{args.num}" must be greater than 0') + # Handle empty file + if os.path.getsize(args.file) == 0: + parser.error(f"File: '{args.file}' appears to be empty") + # Handle file not present if not os.path.isfile(args.file): parser.error(f"No such file or directory: '{args.file}'") @@ -68,13 +74,23 @@ def main(): args = get_args() random.seed(args.seed) - wod = [] with open(args.file, 'rt', encoding='utf-8') as f: exercises = read_csv(f) + # Deal with any badly formed data (read_csv returns None) + if not exercises: + sys.exit(f'No usable data in --file "{args.file}"') + + # Check if trying to sample more exercises than are actually in the file + num_exercises = len(exercises) + if args.num > num_exercises: + sys.exit(f'--num "{args.num}" > exercises "{num_exercises}"') + + wod = [] for name, low, high in random.sample(exercises, k=args.num): suggested_reps = random.randint(low, high) if args.easy: + # Round down any fractional results suggested_reps = suggested_reps // 2 wod.append((name, suggested_reps)) @@ -89,17 +105,16 @@ def read_csv(fh) -> list: exercises = [] for row in reader: name, reps = row - low, high = [int(value) for value in reps.split('-')] - exercises.append((name, low, high)) + # Check each record has both items + if name and reps: + # Check data is as expected for reps (eg. number-number) + match = re.match(r'(\d+)-(\d+)', reps) + if match: + low, high = [int(value) for value in reps.split('-')] + exercises.append((name, low, high)) return exercises - -def test_read_csv(): - """ Test read_csv functions """ - # Make a fake file handle using io module - text = io.StringIO('exercise,reps\nBurpees,20-50\nSitups,40-100') - assert read_csv(text) == [('Burpees', 20, 50), ('Situps', 40, 100)] - +# Removed unit test to separate file. # -------------------------------------------------- if __name__ == '__main__': From ad23046e670999d72c4c124ea789b520c573ba58 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 14 Sep 2021 09:35:48 +0900 Subject: [PATCH 87/98] WOD: Added unit2.py to test for delimiter arg in read_csv. --- 19_wod/unit2.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 19_wod/unit2.py diff --git a/19_wod/unit2.py b/19_wod/unit2.py new file mode 100644 index 000000000..89c71656b --- /dev/null +++ b/19_wod/unit2.py @@ -0,0 +1,28 @@ +import io +from wod import read_csv + + +# -------------------------------------------------- +def test_read_csv(): + """Test read_csv""" + + good = io.StringIO('exercise,reps\nBurpees,20-50\nSitups,40-100') + assert read_csv(good, ',') == [('Burpees', 20, 50), ('Situps', 40, 100)] + + no_data = io.StringIO('') + assert read_csv(no_data, ',') == [] + + headers_only = io.StringIO('exercise,reps\n') + assert read_csv(headers_only, ',') == [] + + # bad_headers = io.StringIO('Exercise,Reps\nBurpees,20-50\nSitups,40-100') + # assert read_csv(bad_headers) == [] + + bad_numbers = io.StringIO('exercise,reps\nBurpees,20-50\nSitups,forty-100') + assert read_csv(bad_numbers, ',') == [('Burpees', 20, 50)] + + no_dash = io.StringIO('exercise,reps\nBurpees,20\nSitups,40-100') + assert read_csv(no_dash, ',') == [('Situps', 40, 100)] + + tabs = io.StringIO('exercise\treps\nBurpees\t20-50\nSitups\t40-100') + assert read_csv(tabs, '\t') == [('Burpees', 20, 50), ('Situps', 40, 100)] From 564947c965f7593d174b55eef9fba5c49b9fc06e Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 14 Sep 2021 09:36:32 +0900 Subject: [PATCH 88/98] WOD: Tried to refactor code to deal with tab delimiter, having issues getting string to carry. --- 19_wod/wod.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/19_wod/wod.py b/19_wod/wod.py index cf4c26f19..c405b1ba0 100755 --- a/19_wod/wod.py +++ b/19_wod/wod.py @@ -8,7 +8,6 @@ import argparse import csv -import io import os import random import re @@ -46,6 +45,11 @@ def get_args(): type=int, default=4) + parser.add_argument('-t', + '--tab', + help='Specify tab delimited file', + action='store_true') + parser.add_argument('-e', '--easy', help='Halve the reps', @@ -99,10 +103,14 @@ def main(): def read_csv(fh) -> list: """ Read CSV formatted input, returns a list of tuples """ + exercises = [] + if fh.seek(0, os.SEEK_END) == 0: + return exercises + fh.seek(0) + # delimit = '\t' if args.tab else ',' reader = csv.reader(fh) # Skip past headers _ = next(reader) - exercises = [] for row in reader: name, reps = row # Check each record has both items @@ -116,6 +124,7 @@ def read_csv(fh) -> list: # Removed unit test to separate file. + # -------------------------------------------------- if __name__ == '__main__': main() From ea1ca74ea411cf0db8fc24bb90d2a95a4958799d Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 14 Sep 2021 09:43:15 +0900 Subject: [PATCH 89/98] WOD: Got tab-delimited files working properly. --- 19_wod/wod.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/19_wod/wod.py b/19_wod/wod.py index c405b1ba0..174f01d4d 100755 --- a/19_wod/wod.py +++ b/19_wod/wod.py @@ -79,7 +79,7 @@ def main(): args = get_args() random.seed(args.seed) with open(args.file, 'rt', encoding='utf-8') as f: - exercises = read_csv(f) + exercises = read_csv(f, tab=True) if args.tab else read_csv(f) # Deal with any badly formed data (read_csv returns None) if not exercises: @@ -101,14 +101,14 @@ def main(): print(tabulate(wod, headers=('Exercise', 'Reps'))) -def read_csv(fh) -> list: +def read_csv(fh, tab=False) -> list: """ Read CSV formatted input, returns a list of tuples """ exercises = [] if fh.seek(0, os.SEEK_END) == 0: return exercises fh.seek(0) - # delimit = '\t' if args.tab else ',' - reader = csv.reader(fh) + delimit = '\t' if tab else ',' + reader = csv.reader(fh, delimiter=delimit) # Skip past headers _ = next(reader) for row in reader: From efe9e315548246650ef36bfde8b54a04986ba202 Mon Sep 17 00:00:00 2001 From: Franco Date: Tue, 14 Sep 2021 18:08:46 +0900 Subject: [PATCH 90/98] WOD: Added option to choose tblfmt for tabulate. --- 19_wod/wod.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/19_wod/wod.py b/19_wod/wod.py index 174f01d4d..366e5da96 100755 --- a/19_wod/wod.py +++ b/19_wod/wod.py @@ -45,6 +45,14 @@ def get_args(): type=int, default=4) + parser.add_argument('-d', + '--display', + help='Choose format of how to display output', + metavar='display', + choices=['plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', + 'mediawiki', 'latex', 'latex_raw', 'latex_booktabs'], + default='plain') + parser.add_argument('-t', '--tab', help='Specify tab delimited file', @@ -98,7 +106,7 @@ def main(): suggested_reps = suggested_reps // 2 wod.append((name, suggested_reps)) - print(tabulate(wod, headers=('Exercise', 'Reps'))) + print(tabulate(wod, headers=('Exercise', 'Reps'), tablefmt=args.display)) def read_csv(fh, tab=False) -> list: From 848b9faceab862827c7e538932f7e16ba8be9a79 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 15 Sep 2021 09:28:56 +0900 Subject: [PATCH 91/98] Amended .gitignore to allow password.py to be added. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 695e02740..00fbe7500 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,6 @@ venv/ # 17_mad_libs/mad.py # 18_gematria/gematria.py # 19_wod/wod.py -20_password/password.py +# 20_password/password.py 21_tictactoe/tictactoe.py +inputs/word* From 5b59042405912522c30e3267df79119de8c4b530 Mon Sep 17 00:00:00 2001 From: Franco Date: Wed, 15 Sep 2021 09:29:50 +0900 Subject: [PATCH 92/98] PASSWORD: First attempt - passes all tests. --- 20_password/password.py | 138 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100755 20_password/password.py diff --git a/20_password/password.py b/20_password/password.py new file mode 100755 index 000000000..0fa623673 --- /dev/null +++ b/20_password/password.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +""" +Title : XKCD Password generator +Author : wrjt +Date : 2021-09-14 +Purpose: To generate passphrases based on dictionary words +""" + +import argparse +import os +import random +import re +import string + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Password maker', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', + metavar='file', + type=str, + nargs="+", + help='Input file(s)') + + parser.add_argument('-n', + '--num', + help='Number of passwords to generate', + metavar='num_passwords', + type=int, + default=3) + + parser.add_argument('-w', + '--num_words', + help='Number of words to use for password', + metavar='num_words', + type=int, + default=4) + + parser.add_argument('-m', + '--min_word_len', + help='Minimum word length', + metavar='minimum', + type=int, + default=3) + + parser.add_argument('-x', + '--max_word_len', + help='Maximum word length', + metavar='maximum', + type=int, + default=6) + + parser.add_argument('-s', + '--seed', + help='Random seed', + metavar='seed', + type=int, + default=None) + + parser.add_argument('-l', + '--l33t', + help='Obsfucate letters', + action='store_true') + + args = parser.parse_args() + + # Handle file not present (nargs makes it a list) + for f in args.file: + if not os.path.isfile(f): + parser.error(f"No such file or directory: '{f}'") + + return args + + +# -------------------------------------------------- +def main(): + """ Main Prog """ + + args = get_args() + random.seed(args.seed) + words = set() + short, long = args.min_word_len, args.max_word_len + + for f in args.file: + with open(f, 'rt', encoding='utf-8') as fh: + for line in fh: + for word in map(clean, line.lower().split()): + if short <= len(word) <= long: + words.add(word.title()) + + words = sorted(words) + + passwords = [ + ''.join(random.sample(words, args.num_words)) for _ in range(args.num) + ] + + if args.l33t: + passwords = [l33t(word) for word in passwords] + + print('\n'.join(passwords)) + + +def clean(text: str) -> str: + """ Clean inputted text of non-alphabet characters """ + return re.sub(re.compile(r'[^A-Za-z]'), '', text) + + +def ransom(text: str) -> str: + """ Randomly CApITalIzE letters in a word """ + ransomed = [ + c.upper() if random.choice([0, 1]) else c.lower() for c in text + ] + return ''.join(ransomed) + + +def l33t(text: str) -> str: + """ Make text l33t like """ + leet_me = { + 'a': '@', + 'A': '4', + 'O': '0', + 't': '+', + 'E': '3', + 'I': '1', + 'S': '5' + } + leeted = ransom(text).translate(str.maketrans(leet_me)) + return leeted + random.choice(string.punctuation) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 094afdef356da7e447c0569ed621b5f42f57cabf Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 16 Sep 2021 09:55:02 +0900 Subject: [PATCH 93/98] Amended .gitignore to allow tictactoe.py to be added. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 00fbe7500..0126fbf13 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,5 @@ venv/ # 18_gematria/gematria.py # 19_wod/wod.py # 20_password/password.py -21_tictactoe/tictactoe.py +# 21_tictactoe/tictactoe.py inputs/word* From d6dc32ff4ed4bc9ed41aed1bb151f00715c8ab78 Mon Sep 17 00:00:00 2001 From: Franco Date: Thu, 16 Sep 2021 09:55:37 +0900 Subject: [PATCH 94/98] TICTACTOE: First attempt - passes all tests. --- 21_tictactoe/tictactoe.py | 107 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 21_tictactoe/tictactoe.py diff --git a/21_tictactoe/tictactoe.py b/21_tictactoe/tictactoe.py new file mode 100755 index 000000000..c3d858d2e --- /dev/null +++ b/21_tictactoe/tictactoe.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Title : Tictactoe +Author : wrjt +Date : 2021-09-15 +Purpose: Emulate a game of tictactoe +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Tictactoe', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-b', + '--board', + help='Current state of the board', + metavar='board', + type=str, + default='.........') + + parser.add_argument('-p', + '--player', + help='Player to move', + metavar='player', + type=str, + choices=["X", "O"]) + + parser.add_argument('-c', + '--cell', + help='Number of the cell to be taken', + metavar='cell', + type=int, + choices=[1, 2, 3, 4, 5, 6, 7, 8, 9]) + + args = parser.parse_args() + + if len(args.board) != 9: + parser.error(f'--board "{args.board}" must be 9 characters of ., X, O') + + for char in args.board: + if char not in ".XO": + parser.error( + f'--board "{args.board}" must be 9 characters of ., X, O') + + if (args.player and not args.cell) or (args.cell and not args.player): + parser.error("Must provide both --player and --cell") + + if args.cell: + if args.board[args.cell - 1] in "XO": + parser.error(f'--cell "{args.cell}" already taken') + + return args + + +# -------------------------------------------------- +def main(): + """ Main Prog""" + + args = get_args() + board = args.board + + if args.cell: + pos = args.cell + board = board[:pos - 1] + args.player + board[pos:] + + winner = find_winner(board, 'O') or find_winner(board, 'X') or "No winner." + + print(format_board(board) + '\n' + winner) + + +def format_board(board: str) -> str: + """ Print out current state of board """ + cells = [str(i + 1) if c == '.' else c for i, c in enumerate(board)] + divider = "-------------" + cells_tmpl = "| {} | {} | {} |" + return '\n'.join([ + divider, + cells_tmpl.format(*cells[:3]), divider, + cells_tmpl.format(*cells[3:6]), divider, + cells_tmpl.format(*cells[6:]), divider + ]) + + +def find_winner(board: str, player: str) -> str: + """ Return True if winning combination """ + wins = [('PPP......'), ('...PPP...'), ('......PPP'), ('P..P..P..'), + ('.P..P..P.'), ('..P..P..P'), ('P...P...P'), ('..P.P.P..')] + board = ''.join([c.replace(c, '.') if c != player else c for c in board]) + for win in wins: + matches = 0 + win = win.replace('P', player) + for w, b in zip(win, board): + if w == player and b == player: + matches += 1 + if matches == 3: + return f"{player} has won!" + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From dba0ce6dc3f6937f9abae514e17adfa1a4952a99 Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 17 Sep 2021 08:16:48 +0900 Subject: [PATCH 95/98] TICTACTOE: Refactored get_args(). --- 21_tictactoe/tictactoe.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/21_tictactoe/tictactoe.py b/21_tictactoe/tictactoe.py index c3d858d2e..ac7fefa40 100755 --- a/21_tictactoe/tictactoe.py +++ b/21_tictactoe/tictactoe.py @@ -7,6 +7,7 @@ """ import argparse +import re # -------------------------------------------------- @@ -22,37 +23,33 @@ def get_args(): help='Current state of the board', metavar='board', type=str, - default='.........') + default='.' * 9) parser.add_argument('-p', '--player', help='Player to move', metavar='player', type=str, - choices=["X", "O"]) + choices="XO", + default=None) parser.add_argument('-c', '--cell', help='Number of the cell to be taken', metavar='cell', type=int, - choices=[1, 2, 3, 4, 5, 6, 7, 8, 9]) + choices=range(1, 10), + default=None) args = parser.parse_args() - if len(args.board) != 9: + if not re.search(r'^[.XO]{9}$', args.board): parser.error(f'--board "{args.board}" must be 9 characters of ., X, O') - for char in args.board: - if char not in ".XO": - parser.error( - f'--board "{args.board}" must be 9 characters of ., X, O') - if (args.player and not args.cell) or (args.cell and not args.player): parser.error("Must provide both --player and --cell") - if args.cell: - if args.board[args.cell - 1] in "XO": + if args.cell and args.player and args.board[args.cell - 1] in "XO": parser.error(f'--cell "{args.cell}" already taken') return args From 100ef770eda2818e70f857177392ec0574cca58b Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 17 Sep 2021 09:07:19 +0900 Subject: [PATCH 96/98] TICTACTOE: Added a start value to enumerate. --- 21_tictactoe/tictactoe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/21_tictactoe/tictactoe.py b/21_tictactoe/tictactoe.py index ac7fefa40..22ce8fd04 100755 --- a/21_tictactoe/tictactoe.py +++ b/21_tictactoe/tictactoe.py @@ -50,7 +50,7 @@ def get_args(): parser.error("Must provide both --player and --cell") if args.cell and args.player and args.board[args.cell - 1] in "XO": - parser.error(f'--cell "{args.cell}" already taken') + parser.error(f'--cell "{args.cell}" already taken') return args @@ -73,7 +73,7 @@ def main(): def format_board(board: str) -> str: """ Print out current state of board """ - cells = [str(i + 1) if c == '.' else c for i, c in enumerate(board)] + cells = [str(i) if c == '.' else c for i, c in enumerate(board, start=1)] divider = "-------------" cells_tmpl = "| {} | {} | {} |" return '\n'.join([ @@ -85,7 +85,7 @@ def format_board(board: str) -> str: def find_winner(board: str, player: str) -> str: - """ Return True if winning combination """ + """ Return the winner """ wins = [('PPP......'), ('...PPP...'), ('......PPP'), ('P..P..P..'), ('.P..P..P.'), ('..P..P..P'), ('P...P...P'), ('..P.P.P..')] board = ''.join([c.replace(c, '.') if c != player else c for c in board]) From ac87b34ae8d8d079219f1373bb58b8ae272a4d0f Mon Sep 17 00:00:00 2001 From: Franco Date: Fri, 17 Sep 2021 20:34:23 +0900 Subject: [PATCH 97/98] ITICTACTOE: First effort using strings and a shile loop. --- 22_itictactoe/itictactoe.py | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100755 22_itictactoe/itictactoe.py diff --git a/22_itictactoe/itictactoe.py b/22_itictactoe/itictactoe.py new file mode 100755 index 000000000..e11c337e2 --- /dev/null +++ b/22_itictactoe/itictactoe.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Title : ITictactoe +Author : wrjt +Date : 2021-09-15 +Purpose: Play an interactive game of tictactoe +""" + +import os +import sys + + +def main() -> None: + """ Main Prog""" + + board = '.' * 9 + player = 'X' + print('\n' + format_board(board)) + + while True: + move = input(f"Player {player}, what is your move? [q to quit]: ") + if move == 'q': + sys.exit("You lose, loser!") + try: + move = int(move) + if move not in range(1, 10): + raise ValueError + except ValueError: + print(f'Invalid cell "{move}", please use 1-9') + continue + else: + if board[move - 1] in "XO": + print(f'Cell "{move}" already taken.') + else: + board = board[:move - 1] + player + board[move:] + print('\n' + format_board(board)) + if find_winner(board, player): + sys.exit(f"{player} has won!") + if "." not in board: + sys.exit("All right, we'll call it a draw.") + player = 'O' if player == 'X' else 'X' + + +def clear() -> None: + """ Clear screen (depending on OS) """ + if os.name == 'nt': + _ = os.system('cls') + else: + _ = os.system('clear') + + +def format_board(board: str) -> str: + """ Print out current state of board """ + clear() + cells = [str(i) if c == '.' else c for i, c in enumerate(board, start=1)] + divider = "-------------" + cells_tmpl = "| {} | {} | {} |" + return '\n'.join([ + divider, + cells_tmpl.format(*cells[:3]), divider, + cells_tmpl.format(*cells[3:6]), divider, + cells_tmpl.format(*cells[6:]), divider + ]) + + +def find_winner(board: str, player: str) -> bool: + """ Return the winner """ + wins = [('PPP......'), ('...PPP...'), ('......PPP'), ('P..P..P..'), + ('.P..P..P.'), ('..P..P..P'), ('P...P...P'), ('..P.P.P..')] + board = ''.join([c.replace(c, '.') if c != player else c for c in board]) + for win in wins: + matches = 0 + win = win.replace('P', player) + for w, b in zip(win, board): + if w == player and b == player: + matches += 1 + if matches == 3: + return True + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 4f2e4dd469d913db2f8d5db010f39da20c5b071f Mon Sep 17 00:00:00 2001 From: Franco Date: Sat, 18 Jun 2022 20:15:31 +0900 Subject: [PATCH 98/98] Added cat files from folder 06 --- 06_wc/cat.py | 85 +++++++++++++++++++++ 06_wc/test_cat.py | 183 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100755 06_wc/cat.py create mode 100644 06_wc/test_cat.py diff --git a/06_wc/cat.py b/06_wc/cat.py new file mode 100755 index 000000000..000957103 --- /dev/null +++ b/06_wc/cat.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" +Title : Cat emulation +Author : wrjt +Date : 2021-08-26 +Purpose: Make a python version of the command 'cat' +""" + +import argparse +import io +import os +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Emulate the cat command', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('input', + metavar='str', + nargs='*', + type=str, + default=[], + help='Input file(s)') + + # parser.add_argument('-c', + # '--chars', + # help='Show number of bytes', + # action='store_true') + + # parser.add_argument('-w', + # '--words', + # help='Show number of words', + # action='store_true') + + # parser.add_argument('-l', + # '--lines', + # help='Show number of lines', + # action='store_true') + + return parser.parse_args() + + +# -------------------------------------------------- +# def select_data(first_data: int, second_data: int, third_data: int, +# flags: str) -> None: +# """ Choose what to print, depending on flags """ +# if not flags: +# return f"{first_data:8}{second_data:8}{third_data:8}" +# information = '' +# if 'l' in flags: +# information += f"{first_data:8}" +# if 'w' in flags: +# information += f"{second_data:8}" +# if 'c' in flags: +# information += f"{third_data:8}" +# return information + + +# -------------------------------------------------- +def main() -> None: + """ Main prog here """ + + args = get_args() + + # print(args.input) + + for entry in args.input: + # Check if input is a stream of text (rather than a file) + if entry in (None, '-'): + print(sys.stdin.readline().strip()) + elif os.path.isfile(entry): + with open(entry, "rt", encoding="utf-8") as fh: + print(fh.read()) + else: + print("Unknown input") + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/06_wc/test_cat.py b/06_wc/test_cat.py new file mode 100644 index 000000000..a8ef28c4e --- /dev/null +++ b/06_wc/test_cat.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""tests for wc.py""" + +import os +import random +import re +import string +from subprocess import getstatusoutput + +prg = './cat.py' +empty = './inputs/empty.txt' +one_line = './inputs/one.txt' +two_lines = './inputs/two.txt' +fox = '../inputs/fox.txt' +sonnet = '../inputs/sonnet-29.txt' + + +# -------------------------------------------------- +def test_exists(): + """exists""" + + assert os.path.isfile(prg) + + +# -------------------------------------------------- +def test_usage(): + """usage""" + + for flag in ['-h', '--help']: + rv, out = getstatusoutput(f'{prg} {flag}') + assert rv == 0 + assert re.match("usage", out, re.IGNORECASE) + + +# -------------------------------------------------- +def random_string(): + """generate a random string""" + + k = random.randint(5, 10) + return ''.join(random.choices(string.ascii_letters + string.digits, k=k)) + + +# -------------------------------------------------- +def test_bad_file(): + """bad_file""" + + bad = random_string() + rv, out = getstatusoutput(f'{prg} {bad}') + assert rv != 0 + assert re.search(f"No such file or directory: '{bad}'", out) + + +# # -------------------------------------------------- +# def test_empty(): +# """Test on empty""" + +# rv, out = getstatusoutput(f'{prg} {empty}') +# assert rv == 0 +# assert out.rstrip() == ' 0 0 0 ./inputs/empty.txt' + + +# # -------------------------------------------------- +# def test_one(): +# """Test on one""" + +# rv, out = getstatusoutput(f'{prg} {one_line}') +# assert rv == 0 +# assert out.rstrip() == ' 1 1 2 ./inputs/one.txt' + + +# # -------------------------------------------------- +# def test_two(): +# """Test on two""" + +# rv, out = getstatusoutput(f'{prg} {two_lines}') +# assert rv == 0 +# assert out.rstrip() == ' 2 2 4 ./inputs/two.txt' + + +# # -------------------------------------------------- +# def test_fox(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox}') +# assert rv == 0 +# assert out.rstrip() == ' 1 9 45 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_more(): +# """Test on more than one file""" + +# rv, out = getstatusoutput(f'{prg} {fox} {sonnet}') +# expected = (' 1 9 45 ../inputs/fox.txt\n' +# ' 17 118 661 ../inputs/sonnet-29.txt\n' +# ' 18 127 706 total') +# assert rv == 0 +# assert out.rstrip() == expected + + +# # -------------------------------------------------- +# def test_stdin(): +# """Test on stdin""" + +# rv, out = getstatusoutput(f'{prg} < {fox}') +# assert rv == 0 +# assert out.rstrip() == ' 1 9 45 ' + + +# # -------------------------------------------------- +# def test_oneline_show_lines_only(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -l') +# assert rv == 0 +# assert out.rstrip() == ' 1 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_oneline_show_words_only(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -w') +# assert rv == 0 +# assert out.rstrip() == ' 9 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_oneline_show_bytes_only(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -c') +# assert rv == 0 +# assert out.rstrip() == ' 45 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_oneline_show_lines_words(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -wl') +# assert rv == 0 +# assert out.rstrip() == ' 1 9 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_oneline_show_lines_chars(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -lc') +# assert rv == 0 +# assert out.rstrip() == ' 1 45 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_oneline_show_words_chars(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -wc') +# assert rv == 0 +# assert out.rstrip() == ' 9 45 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_oneline_show_lines_words_chars(): +# """Test on fox""" + +# rv, out = getstatusoutput(f'{prg} {fox} -wlc') +# assert rv == 0 +# assert out.rstrip() == ' 1 9 45 ../inputs/fox.txt' + + +# # -------------------------------------------------- +# def test_more_lines_chars_only(): +# """Test on more than one file""" + +# rv, out = getstatusoutput(f'{prg} {fox} {sonnet} -lc') +# expected = (' 1 45 ../inputs/fox.txt\n' +# ' 17 661 ../inputs/sonnet-29.txt\n' +# ' 18 706 total') +# assert rv == 0 +# assert out.rstrip() == expected +