From 246bed5bc9ce517c78f83a33f04275d45cd63900 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 22 Jan 2025 01:03:38 +0000 Subject: [PATCH 01/22] testing commit --- .replit | 8 ++++++ bin/new.py | 2 ++ testing.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 .replit create mode 100755 testing.py diff --git a/.replit b/.replit new file mode 100644 index 0000000..688516d --- /dev/null +++ b/.replit @@ -0,0 +1,8 @@ +modules = ["python-3.12", "bash"] +run = "~/workspace/assignments/grade_hw.sh" + +[nix] +channel = "stable-24_05" + +[deployment] +run = ["sh", "-c", "~/workspace/assignments/grade_hw.sh"] diff --git a/bin/new.py b/bin/new.py index 8a2cf7b..be10290 100755 --- a/bin/new.py +++ b/bin/new.py @@ -192,3 +192,5 @@ def get_defaults(): # -------------------------------------------------- if __name__ == '__main__': main() + + diff --git a/testing.py b/testing.py new file mode 100755 index 0000000..34c95cf --- /dev/null +++ b/testing.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +""" +Author : Add your Name +Date : 2025-01-22 +Purpose: This is a test script +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='This is a test script', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('positional', + metavar='str', + help='A positional argument') + + parser.add_argument('-a', + '--arg', + help='A named string argument', + metavar='str', + type=str, + default='') + + parser.add_argument('-i', + '--int', + help='A named integer argument', + metavar='int', + type=int, + default=0) + + parser.add_argument('-f', + '--file', + help='A readable file', + metavar='FILE', + type=argparse.FileType('rt'), + default=None) + + parser.add_argument('-o', + '--on', + help='A boolean flag', + action='store_true') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + str_arg = args.arg + int_arg = args.int + file_arg = args.file + flag_arg = args.on + pos_arg = args.positional + + 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 60f627d615e105aae9dbe36f087e86eb658bb965 Mon Sep 17 00:00:00 2001 From: Bonnie Hurwitz Date: Sun, 23 Mar 2025 20:20:28 -0400 Subject: [PATCH 02/22] fixing test.py in 07_syndna --- assignments/07_syndna/test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/assignments/07_syndna/test.py b/assignments/07_syndna/test.py index 4e08726..0f8f5a3 100644 --- a/assignments/07_syndna/test.py +++ b/assignments/07_syndna/test.py @@ -7,12 +7,16 @@ import string from subprocess import getstatusoutput from Bio import SeqIO -from Bio.SeqUtils import GC +#from Bio.SeqUtils import GC +from Bio.SeqUtils import gc_fraction from numpy import mean from itertools import chain prg = './syndna.py' +# -------------------------------------------------- +def GC(sequence): + return 100 * gc_fraction(sequence, ambiguous="ignore") # -------------------------------------------------- def random_string(): From 3810cdfdbde3f11d351bec54412a0f58df5df197 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Mon, 24 Mar 2025 02:58:21 +0000 Subject: [PATCH 03/22] Assignment 1 commit and exercise 1 --- assignments/01_howdy/howdy.py | 46 +++++++++++++++++++++++++++++++++++ exercises/01_hello/hello.py | 20 +++++++++++++++ 2 files changed, 66 insertions(+) create mode 100755 assignments/01_howdy/howdy.py create mode 100755 exercises/01_hello/hello.py diff --git a/assignments/01_howdy/howdy.py b/assignments/01_howdy/howdy.py new file mode 100755 index 0000000..8189145 --- /dev/null +++ b/assignments/01_howdy/howdy.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-03-24 +Purpose: Print greeting +""" + +import argparse +from enum import Flag + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Print greeting', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-g', '--greeting', default= 'Howdy', metavar= 'str', help='The greeting') + + parser.add_argument('-n','--name', default= 'Stranger', metavar= 'str', help='Whom to greet') + + parser.add_argument('-e','--excited', help='Print ! if this flag is present', action='store_true') + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + greeting = args.greeting + name = args.name + excited = args.excited + + if excited: + print(f'{greeting}, {name}!') + else: + print(f'{greeting}, {name}.') + + + + +# -------------------------------------------------- +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/exercises/01_hello/hello.py b/exercises/01_hello/hello.py new file mode 100755 index 0000000..364af48 --- /dev/null +++ b/exercises/01_hello/hello.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# Purpose: Say Hello +import argparse + + +def get_args(): + parser = argparse.ArgumentParser(description='Say hello') + parser.add_argument('-n', '--name', metavar='name', + default='World', help='Name to greet') + + return parser.parse_args() + + +def main(): + args = get_args() +print('Hello, ' + args.name + '!') + +if __name__ == '__main__': + main() + From 9825362d1edcd589b098b31130389404208a713c Mon Sep 17 00:00:00 2001 From: kukichk0 <39028111-kukichk0@users.noreply.replit.com> Date: Sat, 5 Apr 2025 03:39:17 +0000 Subject: [PATCH 04/22] Checkpoint before assistant change: Fix division by zero error by adding error handling. Replit-Commit-Author: Assistant --- assignments/02_divide/divide.py | 42 +++++++++++++++++++++++++++++ exercises/02_crowsnest/crowsnest.py | 40 +++++++++++++++++++++++++++ exercises/03_picnic/picnic.py | 36 +++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100755 assignments/02_divide/divide.py create mode 100755 exercises/02_crowsnest/crowsnest.py create mode 100755 exercises/03_picnic/picnic.py diff --git a/assignments/02_divide/divide.py b/assignments/02_divide/divide.py new file mode 100755 index 0000000..e0bca5e --- /dev/null +++ b/assignments/02_divide/divide.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-04-03 +Purpose: Add Your Purpose +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + parser = argparse.ArgumentParser( + description='Divide two numbers', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('INT', metavar= 'INT', nargs= 2, type=valid_int, help= 'Numbers to divide') + return parser + +def valid_int(value): + try: + ivalue= int(value) + except ValueError: + raise argparse.ArgumentTypeError(f'invalid int value: {value}') + + return ivalue +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + parser = get_args() + args = parser.parse_args() + numerator, denominator = args.INT + result = numerator // denominator + print(f'{numerator} / {denominator} = {result}') + if args.INT[1] == 0: + parser.error('Cannot divide by zero, dum-dum!') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/exercises/02_crowsnest/crowsnest.py b/exercises/02_crowsnest/crowsnest.py new file mode 100755 index 0000000..fa30671 --- /dev/null +++ b/exercises/02_crowsnest/crowsnest.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +""" +Author : Add your Name +Date : 2025-04-01 +Purpose: Crow's Nest -- choose the correct article +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Crows Nest -- choose the correct article', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('word', + metavar='word', + help= 'A word') + + + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + args = get_args() + word = args.word + + + + if word[0].lower() in 'aeiou': article = 'an' + else: article = 'a' + print(f'Ahoy, Captain, {article} {word} off the larboard bow!') +# ----------------------------------------------- +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/exercises/03_picnic/picnic.py b/exercises/03_picnic/picnic.py new file mode 100755 index 0000000..b6890f8 --- /dev/null +++ b/exercises/03_picnic/picnic.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +""" +Author : Add your Name +Date : 2025-04-03 +Purpose: Add Your Purpose +""" + +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', action='store_true', help='Sort the items') + + + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + args = get_args() + items = args.Item + print(f'You are bringing {items}.') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From e520d382280da57ccdcba788b373f16782d1c9ef Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sat, 5 Apr 2025 03:39:44 +0000 Subject: [PATCH 05/22] Assistant checkpoint: Move zero division check before division operation Assistant generated file changes: - assignments/02_divide/divide.py: Fix zero division error check --- User prompt: how do I fix this ./divide.py 1 0 Traceback (most recent call last): File "/home/runner/workspace/assignments/02_divide/./divide.py", line 42, in main() File "/home/runner/workspace/assignments/02_divide/./divide.py", line 34, in main result = numerator // denominator ~~~~~~~~~~^^~~~~~~~~~~~~ ZeroDivisionError: integer division or modulo by zero Replit-Commit-Author: Assistant Replit-Commit-Session-Id: f998859b-af06-4f4a-bca1-0716a747d838 --- assignments/02_divide/divide.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/assignments/02_divide/divide.py b/assignments/02_divide/divide.py index e0bca5e..d091e28 100755 --- a/assignments/02_divide/divide.py +++ b/assignments/02_divide/divide.py @@ -22,7 +22,7 @@ def valid_int(value): ivalue= int(value) except ValueError: raise argparse.ArgumentTypeError(f'invalid int value: {value}') - + return ivalue # -------------------------------------------------- def main(): @@ -31,10 +31,12 @@ def main(): parser = get_args() args = parser.parse_args() numerator, denominator = args.INT + + if args.INT[1] == 0: + parser.error('Cannot divide by zero, dum-dum!') + result = numerator // denominator print(f'{numerator} / {denominator} = {result}') - if args.INT[1] == 0: - parser.error('Cannot divide by zero, dum-dum!') # -------------------------------------------------- From b366bb002332a5432ab207bf4b2c7333f553b39f Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sat, 5 Apr 2025 03:45:06 +0000 Subject: [PATCH 06/22] Submitting Homework 2 --- assignments/02_divide/divide.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignments/02_divide/divide.py b/assignments/02_divide/divide.py index d091e28..8f039e7 100755 --- a/assignments/02_divide/divide.py +++ b/assignments/02_divide/divide.py @@ -2,7 +2,7 @@ """ Author : Khaira Kukich Date : 2025-04-03 -Purpose: Add Your Purpose +Purpose: Dividing two numbers """ import argparse From 667000f1febe46efbc06ea6add2490f573dc8f4b Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sat, 5 Apr 2025 04:00:57 +0000 Subject: [PATCH 07/22] Fixing Shell Error --- .replit | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.replit b/.replit index 9238a6e..5da2814 100644 --- a/.replit +++ b/.replit @@ -1,7 +1,6 @@ modules = ["python-3.12", "bash"] -======= -modules = ["bash", "python-3.11", "python-3.12"] + run = "~/workspace/assignments/grade_hw.sh" [nix] From c2fa805016901784b282e39b5637e6fec1687ce0 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sat, 5 Apr 2025 04:02:04 +0000 Subject: [PATCH 08/22] Assistant checkpoint: Configure workflow to run Python Assistant generated file changes: - main.py: Create basic Python file - .replit: Updated app configuration --- User prompt: it didnt fix my shell Replit-Commit-Author: Assistant Replit-Commit-Session-Id: 6604206e-2416-4800-9679-4d1cd1e56756 --- .replit | 12 ++++++++++++ main.py | 6 ++++++ 2 files changed, 18 insertions(+) create mode 100644 main.py diff --git a/.replit b/.replit index 5da2814..8c115c8 100644 --- a/.replit +++ b/.replit @@ -8,3 +8,15 @@ channel = "stable-24_05" [deployment] run = ["sh", "-c", "~/workspace/assignments/grade_hw.sh"] + +[workflows] +runButton = "Run" + +[[workflows.workflow]] +name = "Run" +author = 39028111 +mode = "sequential" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "python main.py" diff --git a/main.py b/main.py new file mode 100644 index 0000000..32090c4 --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ + +def main(): + print("Hello from Python!") + +if __name__ == "__main__": + main() From 4d4d687c0739abeec1d3d741a011b9607d9a30b8 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sat, 5 Apr 2025 04:18:53 +0000 Subject: [PATCH 09/22] Submitting Homework 3 --- assignments/03_dna/dna.py | 38 ++++++++++++++++++++++++++++++++++++++ bin/new.py | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100755 assignments/03_dna/dna.py diff --git a/assignments/03_dna/dna.py b/assignments/03_dna/dna.py new file mode 100755 index 0000000..f2ce0b8 --- /dev/null +++ b/assignments/03_dna/dna.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-04-05 +Purpose: +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + parser = argparse.ArgumentParser(description="Tetranucleotide frequency") + parser.add_argument("DNA", help="Input DNA sequence") + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + sequence = args.DNA.upper() + a_count = sequence.count('A') + c_count = sequence.count('C') + g_count = sequence.count('G') + t_count = sequence.count('T') + + print(f'{a_count} {c_count} {g_count} {t_count}') + + + + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/bin/new.py b/bin/new.py index bccd0e1..a696220 100755 --- a/bin/new.py +++ b/bin/new.py @@ -191,4 +191,4 @@ def get_defaults(): # -------------------------------------------------- if __name__ == '__main__': -main() + main() From bd3b8b0e3e2959e5f213eb845dec82daca558f0e Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sat, 5 Apr 2025 04:54:19 +0000 Subject: [PATCH 10/22] Submitting Homework 4 --- assignments/04_revc/revc.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100755 assignments/04_revc/revc.py diff --git a/assignments/04_revc/revc.py b/assignments/04_revc/revc.py new file mode 100755 index 0000000..aabf6f8 --- /dev/null +++ b/assignments/04_revc/revc.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-04-05 +Purpose: Print the Reverse complement of DNA +""" + +import argparse +import os + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Print the reverse complement of DNA', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('DNA', help="Input sequence or file") + + + return parser.parse_args() + +def read_sequence(dna_args): + if os.path.isfile(dna_args): + with open(dna_args) as file: + return file.read().strip() + else: + return dna_args.strip() + +def reverse_complement(sequence): + """Return the reverse complement of a DNA sequence""" + complement = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C', 'a': 't', 't': 'a', 'c': 'g', 'g': 'c'} + return ''.join(complement.get(base, base) for base in reversed(sequence)) +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + sequence = read_sequence(args.DNA) + revc = reverse_complement(sequence) + print(revc) + + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 7b1e033683c46a731b3f95f9d8776d2589d129a5 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Sun, 6 Apr 2025 03:40:53 +0000 Subject: [PATCH 11/22] Assignment 5 --- assignments/05_gc/Rosalind_0808 | 0 assignments/05_gc/Rosalind_5959 | 0 assignments/05_gc/Rosalind_6404 | 0 assignments/05_gc/cgc.py | 65 +++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 assignments/05_gc/Rosalind_0808 create mode 100644 assignments/05_gc/Rosalind_5959 create mode 100644 assignments/05_gc/Rosalind_6404 create mode 100755 assignments/05_gc/cgc.py diff --git a/assignments/05_gc/Rosalind_0808 b/assignments/05_gc/Rosalind_0808 new file mode 100644 index 0000000..e69de29 diff --git a/assignments/05_gc/Rosalind_5959 b/assignments/05_gc/Rosalind_5959 new file mode 100644 index 0000000..e69de29 diff --git a/assignments/05_gc/Rosalind_6404 b/assignments/05_gc/Rosalind_6404 new file mode 100644 index 0000000..e69de29 diff --git a/assignments/05_gc/cgc.py b/assignments/05_gc/cgc.py new file mode 100755 index 0000000..dffa25b --- /dev/null +++ b/assignments/05_gc/cgc.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-04-05 +Purpose: Compute GC content +""" + +import argparse +import os +import sys + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Compute GC content', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("FILE", nargs="?", help='Input sequence file') + + return parser.parse_args() + +def read_fasta(file): + sequences = {} + current_id = None + for line in file: + line = line.strip() + if line.startswith('>'): + current_id = line[1:] + sequences[current_id] = '' + else: + sequences[current_id] += line + return sequences +def gc_content(sequence): + gc = sum(1 for base in sequence if base.upper() in "GC") + return (gc / len(sequence)) * 100 if sequence else 0.0 +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + if args.FILE: + if not os.path.isfile(args.FILE): + print('usage: cgc.py [-h] FILE') + print(f"cgc.py: error: No such file or directory: '{args.FILE}'") + sys.exit(1) + with open(args.FILE) as file: + sequences = read_fasta(file) + else: + sequences = read_fasta(sys.stdin) + max_id = None + max_gc = 0.0 + + for seq_id, sequence in sequences.items(): + gc = gc_content(sequence) + if gc > max_gc: + max_gc = gc + max_id = seq_id + print(f'{max_id} {max_gc:.6f}') + + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From 58026a1c762877256cecad2176ec2b6e19227c38 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Tue, 6 May 2025 19:52:44 +0000 Subject: [PATCH 12/22] Submitting Assignment 6 --- assignments/06_rna/rna.py | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 assignments/06_rna/rna.py diff --git a/assignments/06_rna/rna.py b/assignments/06_rna/rna.py new file mode 100755 index 0000000..1a8decb --- /dev/null +++ b/assignments/06_rna/rna.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-04-05 +Purpose: Transcribe DNA into RNA +""" + +import argparse +import os +import sys +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Transcribe DNA into RNA') + parser.add_argument('files', metavar= 'FILE', nargs='+', help='Input DNA file', type=str) + parser.add_argument('-o', '--out_dir', help='Output directory (default: out', metavar='DIR', default='out') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + out_dir = args.out_dir + files = args.files + + if not os.path.isdir(out_dir): + os.makedirs(out_dir) + + total_sequences = 0 + for file in files: + try: + basename = os.path.basename(file) + out_path = os.path.join(out_dir, basename) + with open(file) as fh_in, open(out_path, 'wt') as fh_out: + for line in fh_in: + rna = line.strip().replace("T", "U") + fh_out.write(rna + '\n') + total_sequences += 1 + except FileNotFoundError: + print(f"usage: {os.path.basename(sys.argv[0])} [-h]", file=sys.stderr) + print(f"No such file or directory: '{file}'", file=sys.stderr) + sys.exit(1) + + num_files = len(files) + seq_str = 'sequence' if total_sequences == 1 else 'sequences' + file_str = 'file' if num_files == 1 else 'files' + print(f'Done, wrote {total_sequences} {seq_str} in {num_files} {file_str} to directory "{out_dir}".') + + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From fc98ec3d85a0791459105fd3600bc0a270cc4518 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Tue, 6 May 2025 20:43:19 +0000 Subject: [PATCH 13/22] Submitting Assignment 7 --- assignments/07_syndna/syndna.py | 117 ++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 assignments/07_syndna/syndna.py diff --git a/assignments/07_syndna/syndna.py b/assignments/07_syndna/syndna.py new file mode 100755 index 0000000..5f74adb --- /dev/null +++ b/assignments/07_syndna/syndna.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-04-26 +Purpose: Create synthetic DNA/RNA sequences +""" + +import argparse +import random +import sys + + +def get_args(): + """Get command-line arguments""" + parser = argparse.ArgumentParser( + description='Create synthetic sequences', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-o', + '--outfile', + type=argparse.FileType('wt'), + default='out.fa', + help='Output filename') + + parser.add_argument('-t', + '--seqtype', + metavar='str', + type=str.lower, + choices=['dna', 'rna'], + default='dna', + help='DNA or RNA') + + parser.add_argument('-n', + '--numseqs', + metavar='int', + type=int, + default=10, + help='Number of sequences to create') + + parser.add_argument('-m', + '--minlen', + metavar='int', + type=int, + default=50, + help='Minimum length') + + parser.add_argument('-x', + '--maxlen', + metavar='int', + type=int, + default=75, + help='Maximum length') + + parser.add_argument('-p', + '--pctgc', + metavar='float', + type=float, + default=0.5, + help='Percent GC') + + parser.add_argument('-s', + '--seed', + metavar='int', + type=int, + default=None, + help='Random seed') + + args = parser.parse_args() + + + if not 0 < args.pctgc < 1: + parser.error(f'--pctgc "{args.pctgc}" must be between 0 and 1') + + + if args.minlen < 1: + parser.error('--minlen must be > 0') + if args.maxlen < args.minlen: + parser.error('--maxlen must be >= --minlen') + + return args + + +def create_pool(pctgc, max_len, seq_type): + """Create the pool of bases""" + t_or_u = 'T' if seq_type == 'dna' else 'U' + num_gc = int((pctgc / 2) * max_len) + num_at = int(((1 - pctgc) / 2) * max_len) + pool = 'A' * num_at + 'C' * num_gc + 'G' * num_gc + t_or_u * num_at + + + for _ in range(max_len - len(pool)): + pool += random.choice(pool) + + return ''.join(sorted(pool)) + + +def main(): + """Main program""" + args = get_args() + random.seed(args.seed) + pool = create_pool(args.pctgc, args.maxlen, args.seqtype) + + for i in range(1, args.numseqs + 1): + seq_len = random.randint(args.minlen, args.maxlen) + seq = ''.join(random.sample(pool, seq_len)) + args.outfile.write(f'>{i}\n{seq}\n') + + args.outfile.close() + + + plural_seq = 'sequence' if args.numseqs == 1 else 'sequences' + print(f'Done, wrote {args.numseqs} {args.seqtype.upper()} {plural_seq} to "{args.outfile.name}".') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From d7e8222082d99c009d4ea3439083d2bb0785ceb1 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 04:29:11 +0000 Subject: [PATCH 14/22] Submitting Assignment 8 --- assignments/08_common/common.py | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 assignments/08_common/common.py diff --git a/assignments/08_common/common.py b/assignments/08_common/common.py new file mode 100755 index 0000000..f532df0 --- /dev/null +++ b/assignments/08_common/common.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-05-06 +Purpose: Find common words between two files +""" + +import argparse +import sys + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Find common words', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file1', + metavar='FILE1', type=argparse.FileType('rt'), + help='Input file 1') + + parser.add_argument('file2', + help='Input file 2', + metavar='FILE2', + type=argparse.FileType('rt')) + + parser.add_argument('-o', + '--outfile', + help='Output file (default: stdout)', + metavar='FILE', + type=argparse.FileType('wt'), + default=sys.stdout) + + return parser.parse_args() +def get_words(filehandle): + words = set() + for line in filehandle: + for word in line.split(): + words.add(word) + return words + +# -------------------------------------------------- +def main(): + """Make a jazz noise here""" + + args = get_args() + + words1 = get_words(args.file1) + words2 = get_words(args.file2) + + common = sorted(words1 & words2) + + for word in common: + print(word, file=args.outfile) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() From a0b0e9b97da6a13301c349abb6e30268f0c1896b Mon Sep 17 00:00:00 2001 From: kukichk0 <39028111-kukichk0@users.noreply.replit.com> Date: Wed, 7 May 2025 14:51:57 +0000 Subject: [PATCH 15/22] Checkpoint before assistant change: Add tools to analyze data, filter records, and annotate BLAST results Implements blastomatic.py for BLAST annotation and csvfilter.py for filtering CSV files based on user input. Replit-Commit-Author: Assistant --- assignments/09_blastomatic/blastomatic.py | 106 ++++++++++++++++++++++ extra_practice/09_csvfilter/csvfilter.py | 46 ++++++++++ 2 files changed, 152 insertions(+) create mode 100755 assignments/09_blastomatic/blastomatic.py create mode 100755 extra_practice/09_csvfilter/csvfilter.py diff --git a/assignments/09_blastomatic/blastomatic.py b/assignments/09_blastomatic/blastomatic.py new file mode 100755 index 0000000..80c558d --- /dev/null +++ b/assignments/09_blastomatic/blastomatic.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-05-07 +Purpose: Annotate BLAST output +""" + +import argparse +import csv +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + parser = argparse.ArgumentParser( + description='Annotate BLAST output', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-b', '--blasthits', + metavar='str', type=argparse.FileType('rt'), required=True, + help='BLAST -outfmt 6') + + parser.add_argument('-a', '--annotations', metavar='str', + help='Annotations file', + type=argparse.FileType('rt'), + required=True) + + parser.add_argument('-o', '--outfile', + help='Output file', + type=argparse.FileType('wt'), + default='out.csv', metavar='str') + + parser.add_argument('-d', '--delimiter', + help='Output field delimiter', + default=',', + metavar='str') + + parser.add_argument('-p', '--pctid', + help='Minimum percent identity', + type=float, metavar='float', + default=0.0) + return parser.parse_args() + + +# -------------------------------------------------- +def guess_delimiter(filename): + with open(filename, 'rt', newline='') as f: + sample = f.read(1024) + sniffer = csv.Sniffer() + return sniffer.sniff(sample).delimiter + + +# -------------------------------------------------- +def main(): + args = get_args() + + try: + ann_delim = guess_delimiter(args.annotations.name) + print(f'Guessed delimiter for annotations file: {ann_delim}') + reader = csv.DictReader(args.annotations, delimiter=ann_delim) + + annotations = {} + for row in reader: + qseqid = row.get('qseqid') + if qseqid: + annotations[qseqid] = row + print(f'Annotations (first 3 rows): {list(annotations.items())[:3]}') + out_fields = ['qseqid', 'pident', 'depth', 'lat_lon'] + writer = csv.DictWriter(args.outfile, fieldnames=out_fields, delimiter=args.delimiter) + writer.writeheader() + + reader = csv.reader(args.blasthits, delimiter='\t') + count = 0 + for row in reader: + if len(row) < 3: + continue + + qseqid = row[0] + try: + pident = float(row[2]) + except ValueError: + continue + print(f'Processing BLAST hit: {qseqid}, pident: {pident}') + if qseqid not in annotations: + print(f'DEBUG: {qseqid} not found in annotations') + elif pident >= args.pctid: + ann = annotations[qseqid] + writer.writerow({ + 'qseqid': qseqid, + 'pident': f'{pident:.3f}', + 'depth': ann.get('depth', ''), + 'lat_lon': ann.get('lat_lon', '') + }) + count += 1 + + print(f'Exported {count} hits to "{args.outfile.name}".') + + except Exception as e: + print(f'Error: {e}', file=sys.stderr) + sys.exit(1) + + + +# -------------------------------------------------- +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/extra_practice/09_csvfilter/csvfilter.py b/extra_practice/09_csvfilter/csvfilter.py new file mode 100755 index 0000000..9952280 --- /dev/null +++ b/extra_practice/09_csvfilter/csvfilter.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Author : Add your Name +Date : 2025-05-07 +Purpose: Add Your Purpose +""" + +import argparse +import csv +import re +import sys +def get_args(): + """Get command-line arguments""" + parser = argparse.ArgumentParser(description='Filter delimited records') + parser.add_argument('-f', '--file', type=argparse.FileType('r'), required=True, help='Input file') + parser.add_argument('-v', '--val', required=True, help='Value for filter') + parser.add_argument('-c', '--col', help='Column name for filter') + parser.add_argument('-o', '--outfile', type=str, default='out.csv', help='Output file name') + parser.add_argument('-d', '--delimiter', default=',', help='Input file delimiter (default: ",")') + return parser.parse_args() +def main(): + args = get_args() + + reader = csv.DictReader(args.file, delimiter=args.delimiter) + fieldnames = reader.fieldnames + + if args.col and args.col not in fieldnames: + print(f'Error: "{args.col}" is not a valid column! Choose from {", ".joinfieldnames}', file=sys.stderr) + sys.exit(1) + + writer = csv.DictWriter(open(args.outfile, 'w', newline=''), fieldnames=fieldnames, delimiter=args.delimiter) + writer.writeheader() + + count = 0 + + for row in reader: + if args.col: + text = row[args.col] + else: + text = " ".join(row.values()) + if re.search(args.val, text, re.IGNORECASE): + writer.writerow(row) + count += 1 + print(f'Done, wrote {count} to "{args.outfile}".') +if __name__ == '__main__': + main() \ No newline at end of file From 428e64041df00279fb4d1784d23ccc1a603709ea Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 14:52:10 +0000 Subject: [PATCH 16/22] Assistant checkpoint: Fix string join syntax in error message Assistant generated file changes: - extra_practice/09_csvfilter/csvfilter.py: Fix string formatting for fieldnames --- User prompt: how do i fix it Replit-Commit-Author: Assistant Replit-Commit-Session-Id: 86e886b0-77b0-4e21-801d-9f59a68a39ac --- extra_practice/09_csvfilter/csvfilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra_practice/09_csvfilter/csvfilter.py b/extra_practice/09_csvfilter/csvfilter.py index 9952280..ecbc0bf 100755 --- a/extra_practice/09_csvfilter/csvfilter.py +++ b/extra_practice/09_csvfilter/csvfilter.py @@ -25,7 +25,7 @@ def main(): fieldnames = reader.fieldnames if args.col and args.col not in fieldnames: - print(f'Error: "{args.col}" is not a valid column! Choose from {", ".joinfieldnames}', file=sys.stderr) + print(f'Error: "{args.col}" is not a valid column! Choose from {", ".join(fieldnames)}', file=sys.stderr) sys.exit(1) writer = csv.DictWriter(open(args.outfile, 'w', newline=''), fieldnames=fieldnames, delimiter=args.delimiter) From a7f73ae095ca28e05aa1394730db665ae7d90182 Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 14:57:23 +0000 Subject: [PATCH 17/22] submitting assignment 9 csvfilter.py --- extra_practice/09_csvfilter/csvfilter.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extra_practice/09_csvfilter/csvfilter.py b/extra_practice/09_csvfilter/csvfilter.py index ecbc0bf..cf1a226 100755 --- a/extra_practice/09_csvfilter/csvfilter.py +++ b/extra_practice/09_csvfilter/csvfilter.py @@ -18,6 +18,7 @@ def get_args(): parser.add_argument('-o', '--outfile', type=str, default='out.csv', help='Output file name') parser.add_argument('-d', '--delimiter', default=',', help='Input file delimiter (default: ",")') return parser.parse_args() + def main(): args = get_args() @@ -25,10 +26,10 @@ def main(): fieldnames = reader.fieldnames if args.col and args.col not in fieldnames: - print(f'Error: "{args.col}" is not a valid column! Choose from {", ".join(fieldnames)}', file=sys.stderr) - sys.exit(1) - - writer = csv.DictWriter(open(args.outfile, 'w', newline=''), fieldnames=fieldnames, delimiter=args.delimiter) + print(f'Error: --col "{args.col}" not a valid column!', file=sys.stderr) + sys.exit(1) + else: + writer = csv.DictWriter(open(args.outfile, 'w', newline=''), fieldnames=fieldnames, delimiter=args.delimiter) writer.writeheader() count = 0 From 51ef593b7e37bcef67895ef72fd3b6ecc5afb57e Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 15:05:27 +0000 Subject: [PATCH 18/22] Submitting assignment 10 !! --- assignments/10_conserved/conserved.py | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 assignments/10_conserved/conserved.py diff --git a/assignments/10_conserved/conserved.py b/assignments/10_conserved/conserved.py new file mode 100755 index 0000000..ef8eeb0 --- /dev/null +++ b/assignments/10_conserved/conserved.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-05-07 +Purpose: Find Conserved Bases +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser(description='Find conserved bases') + parser.add_argument('file', metavar='FILE', type=argparse.FileType('r'), + help='Input file with sequences') + return parser.parse_args() + +def read_sequences(file): + """Read sequences from the file and return them as a list""" + sequences = [] + for line in file: + line = line.strip() + if not line.startswith('>'): + sequences.append(line) + return sequences + +def find_conserved_bases(sequences): + """Find conserved bases in the sequences""" + + if len(set(len(seq) for seq in sequences)) > 1: + raise ValueError("Sequences have different lengths") + + conserved = [] + for i in range(len(sequences[0])): + bases = [seq[i] for seq in sequences] + if len(set(bases)) == 1: + conserved.append('|') + else: + conserved.append('X') + return ''.join(conserved) + +def main(): + args = get_args() + + try: + + sequences = read_sequences(args.file) + + + for seq in sequences: + print(seq) + + + conserved_bases = find_conserved_bases(sequences) + print(conserved_bases) + + except Exception as e: + print(f"Error: {e}") + +if __name__ == '__main__': + main() \ No newline at end of file From 50fb6021e69ca8aeb8f525fc7513a9df173a5347 Mon Sep 17 00:00:00 2001 From: kukichk0 <39028111-kukichk0@users.noreply.replit.com> Date: Wed, 7 May 2025 15:48:07 +0000 Subject: [PATCH 19/22] Checkpoint before assistant change: Add programs to process DNA sequences by encoding and summarizing files Implements run-length encoding in `run.py` and FASTA file summarization in `seqmagique.py` using `argparse` and `tabulate`. Replit-Commit-Author: Assistant --- assignments/11_run_length/run.py | 67 ++++++++++++++++ assignments/12_seqmagique/seqmagique.py | 102 ++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100755 assignments/11_run_length/run.py create mode 100755 assignments/12_seqmagique/seqmagique.py diff --git a/assignments/11_run_length/run.py b/assignments/11_run_length/run.py new file mode 100755 index 0000000..91ba183 --- /dev/null +++ b/assignments/11_run_length/run.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-05-07 +Purpose: Run Length Encoding +""" + +import argparse +import os + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Run-length encoding/data compression') + parser.add_argument('text', + metavar='str', + help='DNA text or file') + return parser.parse_args() + + +def rle(seq): + """Return run-length encoded string""" + if not seq: + return '' + + result = [] + count = 1 + prev = seq[0] + + for base in seq[1:]: + if base == prev: + count += 1 + else: + result.append(prev + (str(count) if count > 1 else '')) + prev = base + count = 1 + result.append(prev + (str(count) if count > 1 else '')) + return ''.join(result) + + +def main(): + args = get_args() + + if os.path.isfile(args.text): + with open(args.text) as fh: + for line in fh: + line = line.strip() + if line: + print(rle(line)) + else: + print(rle(args.text)) + + +def test_rle(): + """Test rle""" + assert rle('') == '' + assert rle('A') == 'A' + assert rle('ACGT') == 'ACGT' + assert rle('AA') == 'A2' + assert rle('AAAAA') == 'A5' + assert rle('ACCGGGTTTT') == 'AC2G3T4' + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/assignments/12_seqmagique/seqmagique.py b/assignments/12_seqmagique/seqmagique.py new file mode 100755 index 0000000..64ba6b6 --- /dev/null +++ b/assignments/12_seqmagique/seqmagique.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-05-07 +Purpose: Summarize FASTA file +""" + +import argparse +import os +import sys +from tabulate import tabulate +from typing import List + + +# -------------------------------------------------- +def get_args(): + """Parse command-line arguments""" + parser = argparse.ArgumentParser( + description="Argparse Python script", + ) + parser.add_argument( + "files", + metavar="FILE", + type=str, + nargs="+", + help="Input FASTA file(s)", + ) + parser.add_argument( + "-t", + "--tablefmt", + metavar="table", + type=str, + default="plain", + help="Tabulate table style (default: plain)", + ) + return parser.parse_args() + + +def die(msg: str) -> None: + """Print error message and exit""" + print(f"usage: {sys.argv[0]} [-h] [-t table] FILE [FILE ...]", file=sys.stderr) + print(msg, file=sys.stderr) + sys.exit(1) + + +def parse_fasta(filename: str) -> List[str]: + """Parse FASTA file and return list of sequences""" + if not os.path.isfile(filename): + die(f"No such file or directory: \'{filename}\'") + + sequences = [] + seq = [] + + with open(filename, "rt") as file: + for line in file: + line = line.strip() + if not line: + continue + if line.startswith(">"): + if seq: + sequences.append("".join(seq)) + seq = [] + else: + seq.append(line) + + if seq: + sequences.append("".join(seq)) + + return sequences + + +def main(): + """Main function""" + args = get_args() + headers = ["name", "min_len", "max_len", "avg_len", "num_seqs"] + rows = [] + + for file in args.files: + try: + seqs = parse_fasta(file) + lengths = list(map(len, seqs)) + num_seqs = len(seqs) + min_len = min(lengths) if lengths else 0 + max_len = max(lengths) if lengths else 0 + avg_len = sum(lengths) / num_seqs if num_seqs else 0.00 + + rows.append([ + file, + min_len, + max_len, + f"{avg_len:.2f}", + num_seqs, + ]) + except SystemExit: + sys.exit(1) + + + print(tabulate(rows, headers=headers, tablefmt="plain", numalign="right", stralign="left")) + + +if __name__ == "__main__": + main() \ No newline at end of file From c0e6f5a3f64e04aedf5e4a1055ce1a79ed50887a Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 16:29:42 +0000 Subject: [PATCH 20/22] Assignment 12 --- assignments/12_seqmagique/assert | 0 assignments/12_seqmagique/seqmagique.py | 28 +++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 assignments/12_seqmagique/assert diff --git a/assignments/12_seqmagique/assert b/assignments/12_seqmagique/assert new file mode 100644 index 0000000..e69de29 diff --git a/assignments/12_seqmagique/seqmagique.py b/assignments/12_seqmagique/seqmagique.py index 64ba6b6..191db5d 100755 --- a/assignments/12_seqmagique/seqmagique.py +++ b/assignments/12_seqmagique/seqmagique.py @@ -11,7 +11,7 @@ from tabulate import tabulate from typing import List - +#This one is a doozy, yikes, I'll take the 50% # -------------------------------------------------- def get_args(): """Parse command-line arguments""" @@ -73,7 +73,7 @@ def main(): """Main function""" args = get_args() headers = ["name", "min_len", "max_len", "avg_len", "num_seqs"] - rows = [] + rows = [] for file in args.files: try: @@ -82,20 +82,22 @@ def main(): num_seqs = len(seqs) min_len = min(lengths) if lengths else 0 max_len = max(lengths) if lengths else 0 - avg_len = sum(lengths) / num_seqs if num_seqs else 0.00 - - rows.append([ - file, - min_len, - max_len, - f"{avg_len:.2f}", - num_seqs, - ]) + avg_len = sum(lengths) / num_seqs if num_seqs else 0 + + avg_len = str(int(avg_len)) if avg_len == 0 else f"{avg_len:.2f}" + rows.append([file, min_len, max_len, avg_len, num_seqs]) except SystemExit: sys.exit(1) - - print(tabulate(rows, headers=headers, tablefmt="plain", numalign="right", stralign="left")) + if not rows: + sys.exit(1) + + print(f"{'name':<26} {'min_len':<8} {'max_len':<8} {'avg_len':<8} {'num_seqs':<8}") + for row in rows: + name, min_len, max_len, avg_len, num_seqs = row + avg_len = f"{avg_len:.2f}" if avg_len != 0 else '0' + num_seqs = str(num_seqs) + print(f"{name:<26} {min_len:<8} {max_len:<8} {avg_len:<8} {num_seqs:<8}") if __name__ == "__main__": From 5e3f3fdb67f99ee13304b39129fd0a7f45f545af Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 16:30:40 +0000 Subject: [PATCH 21/22] submitting assignment 11 --- assignments/11_run_length/run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignments/11_run_length/run.py b/assignments/11_run_length/run.py index 91ba183..4a152d7 100755 --- a/assignments/11_run_length/run.py +++ b/assignments/11_run_length/run.py @@ -4,7 +4,7 @@ Date : 2025-05-07 Purpose: Run Length Encoding """ - +#doooone import argparse import os @@ -64,4 +64,4 @@ def test_rle(): if __name__ == '__main__': - main() \ No newline at end of file + main() \ No newline at end of file From b3aae7a995e5292d0b7de9ea913b06eb89c16c6a Mon Sep 17 00:00:00 2001 From: KhairaK Date: Wed, 7 May 2025 17:33:24 +0000 Subject: [PATCH 22/22] Submitting Project 1 --- project/01_caesar/caesar.py | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 project/01_caesar/caesar.py diff --git a/project/01_caesar/caesar.py b/project/01_caesar/caesar.py new file mode 100755 index 0000000..037991d --- /dev/null +++ b/project/01_caesar/caesar.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Author : Khaira Kukich +Date : 2025-05-07 +Purpose: Implement Caesar Shift +""" + +import argparse +import sys +import os + +def build_substitution_table(shift, decode=False): + alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + shifted = alpha[shift:] + alpha[:shift] + if decode: + return dict(zip(shifted, alpha)) + else: + return dict(zip(alpha, shifted)) + +def caesar_shift(text, table): + result = [] + for char in text: + upper_char = char.upper() + if upper_char in table: + result.append(table[upper_char]) + else: + result.append(char) + return ''.join(result) + +def valid_file(filename): + if not os.path.isfile(filename): + raise argparse.ArgumentTypeError( + f"No such file or directory: '{filename}'") + return filename + +def get_args(): + parser = argparse.ArgumentParser( + description="caesar shift", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('FILE', + metavar='FILE', + help='Input file', + type=valid_file) + + parser.add_argument('-n', + '--number', + help='A number to shift', + type=int, + default=3) + + parser.add_argument('-d', + '--decode', + help='Decode the message', + action='store_true') + + parser.add_argument('-o', + '--outfile', + help='Output file (default: stdout)', + type=argparse.FileType('wt'), + default=sys.stdout) + + return parser.parse_args() + +def main(): + args = get_args() + shift = args.number % 26 + sub_table = build_substitution_table(shift, args.decode) + + with open(args.FILE, 'r') as infile: + for line in infile: + transformed = caesar_shift(line.rstrip(), sub_table) + print(transformed.upper(), file=args.outfile) + +if __name__ == '__main__': + main() \ No newline at end of file