diff --git a/.replit b/.replit new file mode 100644 index 0000000..1d973cf --- /dev/null +++ b/.replit @@ -0,0 +1,9 @@ +modules = ["python-3.12", "bash"] +run = "~/workspace/assignments/grade_hw.sh" + +[nix] +channel = "stable-24_05" +packages = ["zip"] + +[deployment] +run = ["sh", "-c", "~/workspace/assignments/grade_hw.sh"] diff --git a/assignments/01_howdy/howdy.py b/assignments/01_howdy/howdy.py new file mode 100755 index 0000000..4ce7785 --- /dev/null +++ b/assignments/01_howdy/howdy.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-02-02 +Purpose: Print greeting +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Print greeting', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-g', + '--greeting', + metavar='str', + type=str, + default='Howdy', + help='The greeting (default: Howdy)') + + parser.add_argument( + '-n', + '--name', + metavar='str', + type=str, + default='Stranger', + help='Whom to greet (default: Stranger)') + + parser.add_argument( + '-e', + '--excited', + action='store_true', + help='Include an exclamation point (default: False)') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Generate and print greeting""" + + args = get_args() + greeting = f"{args.greeting}, {args.name}" + if args.excited: + greeting += '!' + else: + greeting += '.' + + print(greeting) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/02_divide/divide.py b/assignments/02_divide/divide.py new file mode 100755 index 0000000..2fb1073 --- /dev/null +++ b/assignments/02_divide/divide.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-02-09 +Purpose: divide 2 numbers +""" + +import argparse +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Divide two numbers', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('ints', + metavar='INT', + type=int, + nargs=2, + help='Numbers to divide') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Perform division""" + + args = get_args() + num1, num2 = args.ints + + if num2 == 0: + print('usage: divide.py [-h] INT INT', file=sys.stderr) + print('divide.py: error: Cannot divide by zero, dum-dum!', file=sys.stderr) + return 1 + + print(f'{num1} / {num2} = {num1 // num2}') + return 0 + + +# -------------------------------------------------- +if __name__ == '__main__': + sys.exit(main()) diff --git a/assignments/03_dna/dna.py b/assignments/03_dna/dna.py new file mode 100755 index 0000000..e168843 --- /dev/null +++ b/assignments/03_dna/dna.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-02-16 +Purpose: sequencing dna +""" +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Tetranucleotide frequency', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('DNA', + metavar='DNA', + help='Input DNA sequence') + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + """Count tetranucleotide frequency""" + + args = get_args() + dna = args.DNA.upper() + counts = {base: dna.count(base) for base in 'ACGT'} + + print(counts['A'], counts['C'], counts['G'], counts['T']) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/04_revc/revc.py b/assignments/04_revc/revc.py new file mode 100755 index 0000000..b32267f --- /dev/null +++ b/assignments/04_revc/revc.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" +Author :Xavier Griggs +Date : 2025-03-14 +Purpose: Add Your Purpose +""" + +import argparse +import os + + +# -------------------------------------------------- +def get_args(): + parser = argparse.ArgumentParser( + description='Print the reverse complement of DNA', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('dna', + metavar='DNA', + type=str, + help='Input sequence or file') + + args = parser.parse_args() + + if os.path.isfile(args.dna): + args.dna = open(args.dna).read().rstrip() + + return args + + +# -------------------------------------------------- +def reverse_complement(dna): + complement = str.maketrans('ACGTacgt', 'TGCAtgca') + return dna.translate(complement)[::-1] + + +# -------------------------------------------------- +def main(): + args = get_args() + dna = args.dna + + print(reverse_complement(dna)) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/04_revc/test.py b/assignments/04_revc/test.py index 5422ff2..6a765b6 100755 --- a/assignments/04_revc/test.py +++ b/assignments/04_revc/test.py @@ -6,8 +6,8 @@ import re PRG = './revc.py' -TEST1 = ('./inputs/input1.txt', './tests/inputs/output1.txt') -TEST2 = ('./inputs/input2.txt', './tests/inputs/output2.txt') +TEST1 = ('./inputs/input1.txt', './inputs/output1.txt') +TEST2 = ('./inputs/input2.txt', './inputs/output2.txt') # -------------------------------------------------- diff --git a/assignments/05_gc/cgc.py b/assignments/05_gc/cgc.py new file mode 100755 index 0000000..33e16dc --- /dev/null +++ b/assignments/05_gc/cgc.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-15 +Purpose: Compute GC content of DNA sequences +""" +import argparse +import sys + + +# -------------------------------------------------- +def get_args(): + "Get command-line arguments" + + parser = argparse.ArgumentParser( + description='Compute GC content', + formatter_class=argparse.HelpFormatter) + + parser.add_argument('file', + metavar='FILE', + help='Input sequence file', + type=argparse.FileType('rt'), + nargs='?', + default=sys.stdin) + + return parser.parse_args() + + +# -------------------------------------------------- +def read_fasta(file): + "Read a FASTA file and return a dict of {ID: sequence}" + sequences = {} + current_id = None + for line in file: + line = line.strip() + if line.startswith('>'): + current_id = line[1:] + sequences[current_id] = '' + elif current_id: + sequences[current_id] += line + return sequences + + +# -------------------------------------------------- +def gc_content(seq): + "Calculate GC content" + gc_count = seq.count('G') + seq.count('C') + return gc_count / len(seq) if seq else 0 + + +# -------------------------------------------------- +def main(): + "Compute and print ID with highest GC content" + + args = get_args() + sequences = read_fasta(args.file) + + if not sequences: + print('No sequences found', file=sys.stderr) + sys.exit(1) + + max_id = None + max_gc = -1 + + for seq_id, seq in sequences.items(): + gc = gc_content(seq) + if gc > max_gc: + max_gc = gc + max_id = seq_id + + print(f'{max_id} {max_gc * 100:.6f}') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/06_rna/rna.py b/assignments/06_rna/rna.py new file mode 100755 index 0000000..0865e9a --- /dev/null +++ b/assignments/06_rna/rna.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-30 +Purpose: Transcribe DNA sequences into RNA and save to output file""" + +import argparse +import os +import sys + + +# -------------------------------------------------- +def transcribe_dna_to_rna(dna_sequence): + """Transcribe a DNA sequence into RNA by replacing T with U""" + return dna_sequence.replace('T', 'U') + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Transcribe DNA into RNA', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + # Argument for input DNA file + parser.add_argument('files', + metavar='FILE', + nargs='+', + help='One or more input DNA files') + + #default = "out" + parser.add_argument('-o', + '--out_dir', + help='Output directory for RNA files (default: out)', + metavar='DIR', + type=str, + default='out') + + return parser.parse_args() + + +# -------------------------------------------------- +def process_files(args): + """Process the input files, transcribe DNA to RNA, and write to output dir""" + + # Make sure the output dir exists + if not os.path.exists(args.out_dir): + os.makedirs(args.out_dir) + + total_sequences = 0 + for input_file in args.files: + # Check if the file exists + if not os.path.isfile(input_file): + # Only print the usage message (not error message) + print(f"Usage: {sys.argv[0]} [-h] [-o DIR] FILE [FILE ...]") + print(f"No such file or directory: '{input_file}'") + sys.exit(1) # Exit with a non-zero code if file is missing + + # Read the file and process + with open(input_file, 'r') as infile: + lines = infile.readlines() + + # Create an output file + output_file_path = os.path.join(args.out_dir, + os.path.basename(input_file)) + + with open(output_file_path, 'w') as outfile: + for line in lines: + dna_sequence = line.strip() + if dna_sequence: # Only transcribe non-empty lines + rna_sequence = transcribe_dna_to_rna(dna_sequence) + outfile.write(rna_sequence + '\n') + total_sequences += 1 + print( + f'Done, wrote {total_sequences} sequence{"s" if total_sequences > 1 else ""} in {len(args.files)} file{"s" if len(args.files) > 1 else ""} to directory "{args.out_dir}".' + ) + + +# -------------------------------------------------- +def main(): + args = get_args() + process_files(args) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/07_syndna/syndna.py b/assignments/07_syndna/syndna.py new file mode 100755 index 0000000..20225fd --- /dev/null +++ b/assignments/07_syndna/syndna.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-30 +Purpose: +""" + +import argparse +import random +import sys + +# -------------------------------------------------- +def create_pool(pctgc, max_len, seq_type): + """ Create the pool of bases based on GC content and sequence type """ + + 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 get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Create synthetic DNA or RNA sequences', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-o', '--outfile', + help='Output filename (default: out.fa)', + metavar='str', + type=str, + default='out.fa') + + parser.add_argument('-t', '--seqtype', + help='DNA or RNA (default: dna)', + metavar='str', + type=str, + choices=['dna', 'rna'], + default='dna') + + parser.add_argument('-n', '--numseqs', + help='Number of sequences to create (default: 10)', + metavar='int', + type=int, + default=10) + + parser.add_argument('-m', '--minlen', + help='Minimum length (default: 50)', + metavar='int', + type=int, + default=50) + + parser.add_argument('-x', '--maxlen', + help='Maximum length (default: 75)', + metavar='int', + type=int, + default=75) + + parser.add_argument('-p', '--pctgc', + help='Percent GC (default: 0.5)', + metavar='float', + type=float, + default=0.5) + + parser.add_argument('-s', '--seed', + help='Random seed (default: None)', + metavar='int', + type=int, + default=None) + + return parser.parse_args() + + +# -------------------------------------------------- +def main(): + + + args = get_args() + + # Verify that pctgc is between 0 and 1 + if not 0 < args.pctgc < 1: + print(f"Usage: {sys.argv[0]} [-h] [-o DIR] FILE [FILE ...]", end=' ') + print(f'--pctgc "{args.pctgc}" must be between 0 and 1', end=' ') + sys.exit(1) + + # Set random seed + random.seed(args.seed) + + # Create the base pool + pool = create_pool(args.pctgc, args.maxlen, args.seqtype) + + # Open output file + with open(args.outfile, 'w') as outfile: + for i in range(args.numseqs): + seq_len = random.randint(args.minlen, args.maxlen) + seq = ''.join(random.sample(pool, seq_len)) + + # Write to FASTA + outfile.write(f'>{i+1}\n') + outfile.write(f'{seq}\n') + + print(f'Done, wrote {args.numseqs} {args.seqtype.upper()} sequences to "{args.outfile}".') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/07_syndna/test.py b/assignments/07_syndna/test.py index 4e08726..bc8e8ac 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(): @@ -137,4 +141,4 @@ def test_options(): finally: if os.path.isfile(out_file): - os.remove(out_file) + os.remove(out_file) \ No newline at end of file diff --git a/assignments/08_common/common.py b/assignments/08_common/common.py new file mode 100755 index 0000000..904c191 --- /dev/null +++ b/assignments/08_common/common.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-30 +Purpose: +""" + +import argparse +import sys + + +def get_words(filehandle): + """Return a set of all whitespace-separated words from the given file""" + words = set() + for line in filehandle: + for word in line.split(): + words.add(word) + return words + + +def main(): + parser = argparse.ArgumentParser( + description='Find common words', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('FILE1', + type=argparse.FileType('r'), + help='Input file 1') + parser.add_argument('FILE2', + type=argparse.FileType('r'), + help='Input file 2') + parser.add_argument('-o', + '--outfile', + metavar='FILE', + type=argparse.FileType('w'), + help='Output file', + default=sys.stdout) + # Ensure help text for optional arguments uses the expected title + for action_group in parser._action_groups: + if action_group.title == 'options': + action_group.title = 'optional arguments' + args = parser.parse_args() + # Get unique words from both files + words1 = get_words(args.FILE1) + args.FILE1.close() + words2 = get_words(args.FILE2) + args.FILE2.close() + # Find common words and sort them + common_words = sorted(words1 & words2) + # Write results to the specified output (stdout by default) + outfh = args.outfile + for word in common_words: + print(word, file=outfh) + if outfh is not sys.stdout: + outfh.close() + + +if __name__ == '__main__': + main() diff --git a/assignments/08_common/out.fa b/assignments/08_common/out.fa new file mode 100644 index 0000000..daf9f3f --- /dev/null +++ b/assignments/08_common/out.fa @@ -0,0 +1,20 @@ +>1 +CATGGCGGGAAGCTAGCCGCCATCCCACTTTCTTACCAGTGGCGTCATTTGCTATATAAGTCGTGACTGGAAAG +>2 +CGGCGCGACATGATCGCATACTAGCGCAAATCATTATCTGTTACAGTGCGCGCTGATT +>3 +TATCGTTGGCCAGGTCACCAATAAGGGGTCTATTACGCGTACCGCAGCACGCACCGAAATGGGT +>4 +CTTCCAAACCGTTGAGATCTGCTTATGCAATTCACCGGACTTGTCGCGTAGGATAACGA +>5 +TAGCCCTAGTATCAAAACGCAGTGCGGCTAGCGGCTTGGACTTATAGAAAAGGCGCCTGCTTGCACTTAC +>6 +ATTACTTAGCCCGGAGCTTCCGGAAGCAAACTTGTTCGCCATGGTCTAAGTCT +>7 +ATGATCGCCCTTAGTAACCTAGCCGGAACGCTTACGGAAATTTAGTGGACATATCCGTACGCTCTGGGCGCTGTA +>8 +CTGGTCGCAATCAACTTCTGGGGTAGCTGCAACCCGGACGAAGTCCTATCATGTGACCTTGAATCTA +>9 +TAGGGCCGTGTTTCCGAAACTACTACCTAAGGATTTGCAGTTCAATCCGAGAGGTGG +>10 +GTCATCCAGGACGAGTATATAATGTCTATGCTTATGAGGCCACTCACGCGGATCTTATG diff --git a/assignments/09_blastomatic/blastomatic.py b/assignments/09_blastomatic/blastomatic.py new file mode 100755 index 0000000..31bb7a4 --- /dev/null +++ b/assignments/09_blastomatic/blastomatic.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-30 +Purpose: Parse BLAST output and merge w/ metadata +""" + +import argparse +import pandas as pd +import sys +import os + + +# -------------------------------------------------- +def get_args(): + + parser = argparse.ArgumentParser( + description='Annotate BLAST output', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-b', + '--blasthits', + help='BLAST -outfmt 6 file', + metavar='FILE', + required=True) + + parser.add_argument('-a', + '--annotations', + help='Annotations file', + metavar='FILE', + required=True) + + parser.add_argument('-o', + '--outfile', + help='Output file', + metavar='FILE', + default='out.csv') + + parser.add_argument('-d', + '--delimiter', + help='Output field delimiter', + metavar='DELIM', + default=',') + + parser.add_argument('-p', + '--pctid', + help='Minimum percent identity', + metavar='PCTID', + type=float, + default=0.0) + + return parser.parse_args() + + +# -------------------------------------------------- +def guess_delimiter(filename): + """Guess delimiter based on extension""" + ext = os.path.splitext(filename)[-1].lower() + return '\t' if ext in ['.tsv', '.tab', '.txt'] else ',' + + +# -------------------------------------------------- +def main(): + args = get_args() + + if not os.path.isfile(args.blasthits): + sys.exit(f"No such file or directory: '{args.blasthits}'") + + if not os.path.isfile(args.annotations): + sys.exit(f"No such file or directory: '{args.annotations}'") + + # Guess delimiters based on extension + blast_delim = guess_delimiter(args.blasthits) + anno_delim = guess_delimiter(args.annotations) + + try: + blast_df = pd.read_csv(args.blasthits, + header=None, + delimiter=blast_delim) + annotations_df = pd.read_csv(args.annotations, delimiter=anno_delim) + except Exception as e: + sys.exit(f"Error reading input files: {e}") + + # BLAST outfmt 6 headers + blast_df.columns = [ + 'qseqid', 'sseqid', 'pident', 'length', 'mismatch', 'gapopen', + 'qstart', 'qend', 'sstart', 'send', 'evalue', 'bitscore' + ] + + # Rename metadata column for merge + if 'seq_id' in annotations_df.columns and 'qseqid' not in annotations_df.columns: + annotations_df.rename(columns={'seq_id': 'qseqid'}, inplace=True) + + # Filter on percent identity + blast_df = blast_df[blast_df['pident'] >= args.pctid] + + merged = pd.merge(blast_df, annotations_df, on='qseqid', how='left') + + # Select required columns + out_df = merged[['qseqid', 'pident', 'depth', 'lat_lon']] + + out_delim = args.delimiter if args.delimiter != ',' else guess_delimiter( + args.outfile) + + out_df.to_csv(args.outfile, index=False, sep=out_delim) + + print(f'Exported {len(out_df)} to "{args.outfile}".') + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/10_conserved/conserved.py b/assignments/10_conserved/conserved.py new file mode 100755 index 0000000..ac74fb6 --- /dev/null +++ b/assignments/10_conserved/conserved.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-30 +Purpose: Find conserved bases in aligned sequences +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + + parser = argparse.ArgumentParser( + description='Find conserved bases in aligned sequences', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', + metavar='FILE', + help='Input file containing aligned sequences') + + return parser.parse_args() + + +# -------------------------------------------------- +def read_sequences(file): + """Read sequences from a file w/ or w/o FASTA headers""" + + sequences = [] + + with open(file) as f: + for line in f: + line = line.strip() + if not line or line.startswith('>'): + continue + sequences.append(line) + + return sequences + + +# -------------------------------------------------- +def find_conserved(sequences): + """Find conserved bases across sequences""" + + conserved_line = [] + + # For each position in the sequences + for i in range(len(sequences[0])): # All sequences are assumed to have the same length + # Get the base at the current position for all + column = [seq[i] for seq in sequences] + + # Check if all bases are the same + if len(set(column)) == 1: + conserved_line.append('|') + else: + conserved_line.append('X') + + return ''.join(conserved_line) + + +# -------------------------------------------------- +def main(): + + args = get_args() + sequences = read_sequences(args.file) + + # Check all sequences have same length + if not all(len(seq) == len(sequences[0]) for seq in sequences): + raise SystemExit('Error: Sequences are not the same length.') + + for seq in sequences: + print(seq) + print(find_conserved(sequences)) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/11_run_length/run.py b/assignments/11_run_length/run.py new file mode 100755 index 0000000..88edaa2 --- /dev/null +++ b/assignments/11_run_length/run.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +""" +Author : Your Name +Date : 2025-04-30 +Purpose: Compress DNA sequences using Run-Length Encoding (RLE) +""" + +import argparse + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Run-length encoding/data compression for DNA sequences', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('text', + metavar='str', + help='DNA sequence or file containing sequences') + + return parser.parse_args() + + +# -------------------------------------------------- +def rle(seq): + """Create RLE (Run-Length Encoding) for a single sequence""" + + encoded = [] + count = 1 + + for i in range(1, len(seq)): + if seq[i] == seq[i - 1]: + count += 1 + else: + encoded.append(f"{seq[i - 1]}{count if count > 1 else ''}") + count = 1 + + # Append the last base + encoded.append(f"{seq[-1]}{count if count > 1 else ''}") + + return ''.join(encoded) + + +# -------------------------------------------------- +def main(): + """Main function to execute the program""" + + # Get arguments from the user + args = get_args() + + # If the input is a file, read it + if args.text.endswith('.txt') or args.text.endswith('.fa') or args.text.endswith('.fasta'): + with open(args.text, 'r') as f: + for line in f: + seq = line.strip() + if seq: + print(rle(seq)) + else: + # If the input is a single sequence, apply RLE + seq = args.text.strip() + print(rle(seq)) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/12_seqmagique/inputs/1.fa.grid.out b/assignments/12_seqmagique/inputs/1.fa.grid.out index 57ae5d2..1ce216b 100644 --- a/assignments/12_seqmagique/inputs/1.fa.grid.out +++ b/assignments/12_seqmagique/inputs/1.fa.grid.out @@ -1,5 +1,5 @@ -+---------------------+-----------+-----------+-----------+------------+ -| name | min_len | max_len | avg_len | num_seqs | -+=====================+===========+===========+===========+============+ -| ./tests/inputs/1.fa | 50 | 50 | 50.00 | 1 | -+---------------------+-----------+-----------+-----------+------------+ ++---------------+-----------+-----------+-----------+------------+ +| name | min_len | max_len | avg_len | num_seqs | ++===============+===========+===========+===========+============+ +| ./inputs/1.fa | 50 | 50 | 50 | 1 | ++---------------+-----------+-----------+-----------+------------+ diff --git a/assignments/12_seqmagique/inputs/1.fa.latex.out b/assignments/12_seqmagique/inputs/1.fa.latex.out index 6d53375..9613bb7 100644 --- a/assignments/12_seqmagique/inputs/1.fa.latex.out +++ b/assignments/12_seqmagique/inputs/1.fa.latex.out @@ -1,7 +1,7 @@ \begin{tabular}{lrrrr} \hline - name & min\_len & max\_len & avg\_len & num\_seqs \\ + name & min\_len & max\_len & avg\_len & num\_seqs \\ \hline - ./tests/inputs/1.fa & 50 & 50 & 50.00 & 1 \\ + ./inputs/1.fa & 50 & 50 & 50 & 1 \\ \hline \end{tabular} diff --git a/assignments/12_seqmagique/inputs/1.fa.latex_booktabs.out b/assignments/12_seqmagique/inputs/1.fa.latex_booktabs.out index 621babd..a23ce11 100644 --- a/assignments/12_seqmagique/inputs/1.fa.latex_booktabs.out +++ b/assignments/12_seqmagique/inputs/1.fa.latex_booktabs.out @@ -1,7 +1,7 @@ \begin{tabular}{lrrrr} \toprule - name & min\_len & max\_len & avg\_len & num\_seqs \\ + name & min\_len & max\_len & avg\_len & num\_seqs \\ \midrule - ./tests/inputs/1.fa & 50 & 50 & 50.00 & 1 \\ + ./inputs/1.fa & 50 & 50 & 50 & 1 \\ \bottomrule \end{tabular} diff --git a/assignments/12_seqmagique/inputs/1.fa.latex_raw.out b/assignments/12_seqmagique/inputs/1.fa.latex_raw.out index 35af2d8..4005bc1 100644 --- a/assignments/12_seqmagique/inputs/1.fa.latex_raw.out +++ b/assignments/12_seqmagique/inputs/1.fa.latex_raw.out @@ -1,7 +1,7 @@ \begin{tabular}{lrrrr} \hline - name & min_len & max_len & avg_len & num_seqs \\ + name & min_len & max_len & avg_len & num_seqs \\ \hline - ./tests/inputs/1.fa & 50 & 50 & 50.00 & 1 \\ + ./inputs/1.fa & 50 & 50 & 50 & 1 \\ \hline \end{tabular} diff --git a/assignments/12_seqmagique/inputs/1.fa.mediawiki.out b/assignments/12_seqmagique/inputs/1.fa.mediawiki.out index 2bf71ca..2d93452 100644 --- a/assignments/12_seqmagique/inputs/1.fa.mediawiki.out +++ b/assignments/12_seqmagique/inputs/1.fa.mediawiki.out @@ -1,7 +1,7 @@ {| class="wikitable" style="text-align: left;" |+ |- -! name !! align="right"| min_len !! align="right"| max_len !! align="right"| avg_len !! align="right"| num_seqs +! name !! align="right"| min_len !! align="right"| max_len !! align="right"| avg_len !! align="right"| num_seqs |- -| ./tests/inputs/1.fa || align="right"| 50 || align="right"| 50 || align="right"| 50.00 || align="right"| 1 +| ./inputs/1.fa || align="right"| 50 || align="right"| 50 || align="right"| 50 || align="right"| 1 |} diff --git a/assignments/12_seqmagique/inputs/1.fa.orgtbl.out b/assignments/12_seqmagique/inputs/1.fa.orgtbl.out index a8b658f..20c4cd8 100644 --- a/assignments/12_seqmagique/inputs/1.fa.orgtbl.out +++ b/assignments/12_seqmagique/inputs/1.fa.orgtbl.out @@ -1,3 +1,3 @@ -| name | min_len | max_len | avg_len | num_seqs | -|---------------------+-----------+-----------+-----------+------------| -| ./tests/inputs/1.fa | 50 | 50 | 50.00 | 1 | +| name | min_len | max_len | avg_len | num_seqs | +|---------------+-----------+-----------+-----------+------------| +| ./inputs/1.fa | 50 | 50 | 50 | 1 | diff --git a/assignments/12_seqmagique/inputs/1.fa.out b/assignments/12_seqmagique/inputs/1.fa.out index 9d3d99a..f507f38 100644 --- a/assignments/12_seqmagique/inputs/1.fa.out +++ b/assignments/12_seqmagique/inputs/1.fa.out @@ -1,2 +1,2 @@ -name min_len max_len avg_len num_seqs -./tests/inputs/1.fa 50 50 50.00 1 +name min_len max_len avg_len num_seqs +./inputs/1.fa 50 50 50 1 diff --git a/assignments/12_seqmagique/inputs/1.fa.pipe.out b/assignments/12_seqmagique/inputs/1.fa.pipe.out index 1d6c81e..bf78a1e 100644 --- a/assignments/12_seqmagique/inputs/1.fa.pipe.out +++ b/assignments/12_seqmagique/inputs/1.fa.pipe.out @@ -1,3 +1,3 @@ -| name | min_len | max_len | avg_len | num_seqs | -|:--------------------|----------:|----------:|----------:|-----------:| -| ./tests/inputs/1.fa | 50 | 50 | 50.00 | 1 | +| name | min_len | max_len | avg_len | num_seqs | +|:--------------|----------:|----------:|----------:|-----------:| +| ./inputs/1.fa | 50 | 50 | 50 | 1 | diff --git a/assignments/12_seqmagique/inputs/1.fa.plain.out b/assignments/12_seqmagique/inputs/1.fa.plain.out index 9d3d99a..f507f38 100644 --- a/assignments/12_seqmagique/inputs/1.fa.plain.out +++ b/assignments/12_seqmagique/inputs/1.fa.plain.out @@ -1,2 +1,2 @@ -name min_len max_len avg_len num_seqs -./tests/inputs/1.fa 50 50 50.00 1 +name min_len max_len avg_len num_seqs +./inputs/1.fa 50 50 50 1 diff --git a/assignments/12_seqmagique/inputs/1.fa.rst.out b/assignments/12_seqmagique/inputs/1.fa.rst.out index 2f29f5f..5079b88 100644 --- a/assignments/12_seqmagique/inputs/1.fa.rst.out +++ b/assignments/12_seqmagique/inputs/1.fa.rst.out @@ -1,5 +1,5 @@ -=================== ========= ========= ========= ========== -name min_len max_len avg_len num_seqs -=================== ========= ========= ========= ========== -./tests/inputs/1.fa 50 50 50.00 1 -=================== ========= ========= ========= ========== +============= ========= ========= ========= ========== +name min_len max_len avg_len num_seqs +============= ========= ========= ========= ========== +./inputs/1.fa 50 50 50 1 +============= ========= ========= ========= ========== diff --git a/assignments/12_seqmagique/inputs/1.fa.simple.out b/assignments/12_seqmagique/inputs/1.fa.simple.out index 247b140..f5fa96e 100644 --- a/assignments/12_seqmagique/inputs/1.fa.simple.out +++ b/assignments/12_seqmagique/inputs/1.fa.simple.out @@ -1,3 +1,3 @@ -name min_len max_len avg_len num_seqs -------------------- --------- --------- --------- ---------- -./tests/inputs/1.fa 50 50 50.00 1 +name min_len max_len avg_len num_seqs +------------- --------- --------- --------- ---------- +./inputs/1.fa 50 50 50 1 diff --git a/assignments/12_seqmagique/inputs/2.fa.grid.out b/assignments/12_seqmagique/inputs/2.fa.grid.out index 58d769b..23a9fe7 100644 --- a/assignments/12_seqmagique/inputs/2.fa.grid.out +++ b/assignments/12_seqmagique/inputs/2.fa.grid.out @@ -1,5 +1,5 @@ -+---------------------+-----------+-----------+-----------+------------+ -| name | min_len | max_len | avg_len | num_seqs | -+=====================+===========+===========+===========+============+ -| ./tests/inputs/2.fa | 49 | 79 | 64.00 | 5 | -+---------------------+-----------+-----------+-----------+------------+ ++---------------+-----------+-----------+-----------+------------+ +| name | min_len | max_len | avg_len | num_seqs | ++===============+===========+===========+===========+============+ +| ./inputs/2.fa | 49 | 79 | 64 | 5 | ++---------------+-----------+-----------+-----------+------------+ diff --git a/assignments/12_seqmagique/inputs/2.fa.latex.out b/assignments/12_seqmagique/inputs/2.fa.latex.out index 48b48d0..e2b500e 100644 --- a/assignments/12_seqmagique/inputs/2.fa.latex.out +++ b/assignments/12_seqmagique/inputs/2.fa.latex.out @@ -1,7 +1,7 @@ \begin{tabular}{lrrrr} \hline - name & min\_len & max\_len & avg\_len & num\_seqs \\ + name & min\_len & max\_len & avg\_len & num\_seqs \\ \hline - ./tests/inputs/2.fa & 49 & 79 & 64.00 & 5 \\ + ./inputs/2.fa & 49 & 79 & 64 & 5 \\ \hline \end{tabular} diff --git a/assignments/12_seqmagique/inputs/2.fa.latex_booktabs.out b/assignments/12_seqmagique/inputs/2.fa.latex_booktabs.out index ec58aac..7797b91 100644 --- a/assignments/12_seqmagique/inputs/2.fa.latex_booktabs.out +++ b/assignments/12_seqmagique/inputs/2.fa.latex_booktabs.out @@ -1,7 +1,7 @@ \begin{tabular}{lrrrr} \toprule - name & min\_len & max\_len & avg\_len & num\_seqs \\ + name & min\_len & max\_len & avg\_len & num\_seqs \\ \midrule - ./tests/inputs/2.fa & 49 & 79 & 64.00 & 5 \\ + ./inputs/2.fa & 49 & 79 & 64 & 5 \\ \bottomrule \end{tabular} diff --git a/assignments/12_seqmagique/inputs/2.fa.latex_raw.out b/assignments/12_seqmagique/inputs/2.fa.latex_raw.out index 5ff02c9..c51a300 100644 --- a/assignments/12_seqmagique/inputs/2.fa.latex_raw.out +++ b/assignments/12_seqmagique/inputs/2.fa.latex_raw.out @@ -1,7 +1,7 @@ \begin{tabular}{lrrrr} \hline - name & min_len & max_len & avg_len & num_seqs \\ + name & min_len & max_len & avg_len & num_seqs \\ \hline - ./tests/inputs/2.fa & 49 & 79 & 64.00 & 5 \\ + ./inputs/2.fa & 49 & 79 & 64 & 5 \\ \hline \end{tabular} diff --git a/assignments/12_seqmagique/inputs/2.fa.mediawiki.out b/assignments/12_seqmagique/inputs/2.fa.mediawiki.out index 2cde1e8..fb66beb 100644 --- a/assignments/12_seqmagique/inputs/2.fa.mediawiki.out +++ b/assignments/12_seqmagique/inputs/2.fa.mediawiki.out @@ -1,7 +1,7 @@ {| class="wikitable" style="text-align: left;" |+ |- -! name !! align="right"| min_len !! align="right"| max_len !! align="right"| avg_len !! align="right"| num_seqs +! name !! align="right"| min_len !! align="right"| max_len !! align="right"| avg_len !! align="right"| num_seqs |- -| ./tests/inputs/2.fa || align="right"| 49 || align="right"| 79 || align="right"| 64.00 || align="right"| 5 +| ./inputs/2.fa || align="right"| 49 || align="right"| 79 || align="right"| 64 || align="right"| 5 |} diff --git a/assignments/12_seqmagique/inputs/2.fa.orgtbl.out b/assignments/12_seqmagique/inputs/2.fa.orgtbl.out index 80f05be..78f0d17 100644 --- a/assignments/12_seqmagique/inputs/2.fa.orgtbl.out +++ b/assignments/12_seqmagique/inputs/2.fa.orgtbl.out @@ -1,3 +1,3 @@ -| name | min_len | max_len | avg_len | num_seqs | -|---------------------+-----------+-----------+-----------+------------| -| ./tests/inputs/2.fa | 49 | 79 | 64.00 | 5 | +| name | min_len | max_len | avg_len | num_seqs | +|---------------+-----------+-----------+-----------+------------| +| ./inputs/2.fa | 49 | 79 | 64 | 5 | diff --git a/assignments/12_seqmagique/inputs/2.fa.out b/assignments/12_seqmagique/inputs/2.fa.out index 0742e9a..e5ea3e1 100644 --- a/assignments/12_seqmagique/inputs/2.fa.out +++ b/assignments/12_seqmagique/inputs/2.fa.out @@ -1,2 +1,2 @@ -name min_len max_len avg_len num_seqs -./tests/inputs/2.fa 49 79 64.00 5 +name min_len max_len avg_len num_seqs +./inputs/2.fa 49 79 64 5 diff --git a/assignments/12_seqmagique/inputs/2.fa.pipe.out b/assignments/12_seqmagique/inputs/2.fa.pipe.out index 98a8dc3..ab23305 100644 --- a/assignments/12_seqmagique/inputs/2.fa.pipe.out +++ b/assignments/12_seqmagique/inputs/2.fa.pipe.out @@ -1,3 +1,3 @@ -| name | min_len | max_len | avg_len | num_seqs | -|:--------------------|----------:|----------:|----------:|-----------:| -| ./tests/inputs/2.fa | 49 | 79 | 64.00 | 5 | +| name | min_len | max_len | avg_len | num_seqs | +|:--------------|----------:|----------:|----------:|-----------:| +| ./inputs/2.fa | 49 | 79 | 64 | 5 | diff --git a/assignments/12_seqmagique/inputs/2.fa.plain.out b/assignments/12_seqmagique/inputs/2.fa.plain.out index 0742e9a..e5ea3e1 100644 --- a/assignments/12_seqmagique/inputs/2.fa.plain.out +++ b/assignments/12_seqmagique/inputs/2.fa.plain.out @@ -1,2 +1,2 @@ -name min_len max_len avg_len num_seqs -./tests/inputs/2.fa 49 79 64.00 5 +name min_len max_len avg_len num_seqs +./inputs/2.fa 49 79 64 5 diff --git a/assignments/12_seqmagique/inputs/2.fa.rst.out b/assignments/12_seqmagique/inputs/2.fa.rst.out index 905ab33..12a1096 100644 --- a/assignments/12_seqmagique/inputs/2.fa.rst.out +++ b/assignments/12_seqmagique/inputs/2.fa.rst.out @@ -1,5 +1,5 @@ -=================== ========= ========= ========= ========== -name min_len max_len avg_len num_seqs -=================== ========= ========= ========= ========== -./tests/inputs/2.fa 49 79 64.00 5 -=================== ========= ========= ========= ========== +============= ========= ========= ========= ========== +name min_len max_len avg_len num_seqs +============= ========= ========= ========= ========== +./inputs/2.fa 49 79 64 5 +============= ========= ========= ========= ========== diff --git a/assignments/12_seqmagique/inputs/2.fa.simple.out b/assignments/12_seqmagique/inputs/2.fa.simple.out index 227b2d5..3b2e254 100644 --- a/assignments/12_seqmagique/inputs/2.fa.simple.out +++ b/assignments/12_seqmagique/inputs/2.fa.simple.out @@ -1,3 +1,3 @@ -name min_len max_len avg_len num_seqs -------------------- --------- --------- --------- ---------- -./tests/inputs/2.fa 49 79 64.00 5 +name min_len max_len avg_len num_seqs +------------- --------- --------- --------- ---------- +./inputs/2.fa 49 79 64 5 diff --git a/assignments/12_seqmagique/inputs/all.fa.out b/assignments/12_seqmagique/inputs/all.fa.out index 8166b54..017bf38 100644 --- a/assignments/12_seqmagique/inputs/all.fa.out +++ b/assignments/12_seqmagique/inputs/all.fa.out @@ -1,4 +1,4 @@ -name min_len max_len avg_len num_seqs -./tests/inputs/1.fa 50 50 50.00 1 -./tests/inputs/2.fa 49 79 64.00 5 -./tests/inputs/empty.fa 0 0 0.00 0 +name min_len max_len avg_len num_seqs +./inputs/1.fa 50 50 50 1 +./inputs/2.fa 49 79 64 5 +./inputs/empty.fa 0 0 0 0 diff --git a/assignments/12_seqmagique/inputs/empty.fa.out b/assignments/12_seqmagique/inputs/empty.fa.out index 437e8a2..5163912 100644 --- a/assignments/12_seqmagique/inputs/empty.fa.out +++ b/assignments/12_seqmagique/inputs/empty.fa.out @@ -1,2 +1,2 @@ -name min_len max_len avg_len num_seqs -./tests/inputs/empty.fa 0 0 0.00 0 +name min_len max_len avg_len num_seqs +./inputs/empty.fa 0 0 0 0 diff --git a/assignments/12_seqmagique/mk-outs.sh b/assignments/12_seqmagique/mk-outs.sh index 2d2f3db..09f5d7c 100755 --- a/assignments/12_seqmagique/mk-outs.sh +++ b/assignments/12_seqmagique/mk-outs.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash PRG="./seqmagique.py" -DIR="./tests/inputs" +DIR="./inputs" INPUT1="${DIR}/1.fa" INPUT2="${DIR}/2.fa" EMPTY="${DIR}/empty.fa" diff --git a/assignments/12_seqmagique/seqmagique.py b/assignments/12_seqmagique/seqmagique.py new file mode 100755 index 0000000..ad9fae8 --- /dev/null +++ b/assignments/12_seqmagique/seqmagique.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Author : Your Name +Date : 2025-04-30 +Purpose: Process FASTA files and display sequence statistics +""" + +import argparse +from tabulate import tabulate +from Bio import SeqIO +import os +import sys + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Process FASTA files and display sequence statistics', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('files', + metavar='FILE', + nargs='+', + help='FASTA input file(s)') + + parser.add_argument('-t', + '--tablefmt', + metavar='style', + default='plain', + help='Tabulate table style (default: plain)') + + return parser.parse_args() + + +# -------------------------------------------------- +def get_seq_stats(fasta_file): + """Get statistics from a FASTA file""" + + if not os.path.isfile(fasta_file): + print(f"Usage: seqmagique.py [-h] [-t style] FILE [FILE ...]") + print(f"No such file or directory: '{fasta_file}'") + sys.exit(1) + + lengths = [len(rec.seq) for rec in SeqIO.parse(fasta_file, 'fasta')] + if not lengths: + return [fasta_file, 0, 0, "0.00", 0] + + num_seqs = len(lengths) + min_len = min(lengths) + max_len = max(lengths) + avg_len = f"{sum(lengths) / num_seqs:.2f}" + + return [fasta_file, min_len, max_len, avg_len, num_seqs] + + +# -------------------------------------------------- +def main(): + """Main function""" + + args = get_args() + rows = [] + + for file in args.files: + try: + stats = get_seq_stats(file) + if stats: + rows.append(stats) + except Exception as e: + sys.exit(f'Error processing {file}: {e}') + + print(tabulate(rows, + headers=['name', 'min_len', 'max_len', 'avg_len', 'num_seqs'], + tablefmt=args.tablefmt)) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/assignments/12_seqmagique/test.py b/assignments/12_seqmagique/test.py index 72ef89a..cd1e39b 100755 --- a/assignments/12_seqmagique/test.py +++ b/assignments/12_seqmagique/test.py @@ -7,10 +7,10 @@ from subprocess import getstatusoutput, getoutput PRG = './seqmagique.py' -EMPTY = ('./inputs/empty.fa', './tests/inputs/empty.fa.out') -TEST1 = ('./inputs/1.fa', './tests/inputs/1.fa.out') -TEST2 = ('./inputs/2.fa', './tests/inputs/2.fa.out') -ALL = ('./inputs/*.fa', './tests/inputs/all.fa.out') +EMPTY = ('./inputs/empty.fa', './inputs/empty.fa.out') +TEST1 = ('./inputs/1.fa', './inputs/1.fa.out') +TEST2 = ('./inputs/2.fa', './inputs/2.fa.out') +ALL = ('./inputs/*.fa', './inputs/all.fa.out') # -------------------------------------------------- diff --git a/project/01_caesar/caesar.py b/project/01_caesar/caesar.py new file mode 100755 index 0000000..d7bc22b --- /dev/null +++ b/project/01_caesar/caesar.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Author : Xavier Griggs +Date : 2025-04-30 +Purpose: Implement Caesar Shift Cipher for encoding and decoding text files +""" + +import argparse +import os + + +# -------------------------------------------------- +def shift_letter(letter, shift, decode=False): + """Shift a single letter according to Caesar Cipher rules""" + + if letter.isalpha(): + letter = letter.upper() + alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + idx = alpha.index(letter) + + if decode: + shift = -shift + + new_idx = (idx + shift) % 26 + return alpha[new_idx] + else: + return letter + + +# -------------------------------------------------- +def get_args(): + """Get command-line arguments""" + + parser = argparse.ArgumentParser( + description='Encode or decode a message using Caesar Shift Cipher', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('file', + metavar='FILE', + help='Input file to encode or decode', + type=str) + + parser.add_argument('-n', + '--number', + help='Number of positions to shift (default: 3)', + metavar='int', + type=int, + default=3) + + parser.add_argument('-d', + '--decode', + help='Decode the file (default: False)', + action='store_true') + + parser.add_argument( + '-o', + '--outfile', + help='Output file to write the result (default: std.out)', + metavar='FILE', + type=str, + default=None) + + args = parser.parse_args() + + # Check that the file exists + if not os.path.isfile(args.file): + parser.error( + f"can't open '{args.file}': [Errno 2] No such file or directory: '{args.file}'" + ) + + return args + + +# -------------------------------------------------- +def process_file(args): + """Process the input file and apply Caesar Cipher""" + + # Open the input file for reading + with open(args.file, "r") as infile: + lines = infile.readlines() + + # Process each line with the Caesar shift + shifted_lines = [] + for line in lines: + shifted_line = ''.join( + shift_letter(char, args.number, args.decode) for char in line) + shifted_lines.append(shifted_line) + + # Output result to either a file or standard output + if args.outfile: + with open(args.outfile, "w") as outfile: + outfile.writelines(shifted_lines) + else: + for line in shifted_lines: + print(line, end='') + + +# -------------------------------------------------- +def main(): + args = get_args() + process_file(args) + + +# -------------------------------------------------- +if __name__ == '__main__': + main() diff --git a/project/01_caesar/test.py b/project/01_caesar/test.py index 026f2bc..ed3c153 100644 --- a/project/01_caesar/test.py +++ b/project/01_caesar/test.py @@ -14,6 +14,7 @@ BUSTLE = './inputs/bustle.txt' BUSTLE_OUT = './outputs/bustle.txt' + # -------------------------------------------------- def random_string(): """ Generate a random string """ @@ -94,10 +95,8 @@ def test_spiders(): def test_spiders_n(): """ spiders -n 4""" - expected = '\n'.join([ - "HSR'X ASVVC, WTMHIVW,", 'M OIIT LSYWI', - 'GEWYEPPC.' - ]) + expected = '\n'.join( + ["HSR'X ASVVC, WTMHIVW,", 'M OIIT LSYWI', 'GEWYEPPC.']) run([SPIDERS], ['-n 4'], expected) @@ -105,10 +104,8 @@ def test_spiders_n(): def test_spiders_decode(): """ spiders --decode """ - expected = '\n'.join([ - "DON'T WORRY, SPIDERS,", 'I KEEP HOUSE', - 'CASUALLY.' - ]) + expected = '\n'.join( + ["DON'T WORRY, SPIDERS,", 'I KEEP HOUSE', 'CASUALLY.']) run([SPIDERS_OUT], ['--decode'], expected) @@ -117,15 +114,10 @@ def test_bustle(): """ bustle """ expected = '\n'.join([ - 'WKH EXVWOH LQ D KRXVH', - 'WKH PRUQLQJ DIWHU GHDWK', - 'LV VROHPQHVW RI LQGXVWULHV', - 'HQDFWHG XSRQ HDUWK,—', - '', - "WKH VZHHSLQJ XS WKH KHDUW,", - 'DQG SXWWLQJ ORYH DZDB', - 'ZH VKDOO QRW ZDQW WR XVH DJDLQ', - 'XQWLO HWHUQLWB.' + 'WKH EXVWOH LQ D KRXVH', 'WKH PRUQLQJ DIWHU GHDWK', + 'LV VROHPQHVW RI LQGXVWULHV', 'HQDFWHG XSRQ HDUWK,—', '', + "WKH VZHHSLQJ XS WKH KHDUW,", 'DQG SXWWLQJ ORYH DZDB', + 'ZH VKDOO QRW ZDQW WR XVH DJDLQ', 'XQWLO HWHUQLWB.' ]) run([BUSTLE], [], expected) @@ -135,15 +127,11 @@ def test_bustle_n(): """ bustle -n 4""" expected = '\n'.join([ - 'XLI FYWXPI MR E LSYWI', - 'XLI QSVRMRK EJXIV HIEXL', - 'MW WSPIQRIWX SJ MRHYWXVMIW', - "IREGXIH YTSR IEVXL,—", - '', - "XLI WAIITMRK YT XLI LIEVX,", - 'ERH TYXXMRK PSZI EAEC', - 'AI WLEPP RSX AERX XS YWI EKEMR', - 'YRXMP IXIVRMXC.']) + 'XLI FYWXPI MR E LSYWI', 'XLI QSVRMRK EJXIV HIEXL', + 'MW WSPIQRIWX SJ MRHYWXVMIW', "IREGXIH YTSR IEVXL,—", '', + "XLI WAIITMRK YT XLI LIEVX,", 'ERH TYXXMRK PSZI EAEC', + 'AI WLEPP RSX AERX XS YWI EKEMR', 'YRXMP IXIVRMXC.' + ]) run([BUSTLE], ['-n 4'], expected) @@ -152,13 +140,9 @@ def test_bustle_decode(): """ bustle --decode """ expected = '\n'.join([ - 'THE BUSTLE IN A HOUSE', - 'THE MORNING AFTER DEATH', - 'IS SOLEMNEST OF INDUSTRIES', - "ENACTED UPON EARTH,—", - '', - "THE SWEEPING UP THE HEART,", - 'AND PUTTING LOVE AWAY', - 'WE SHALL NOT WANT TO USE AGAIN', - 'UNTIL ETERNITY.']) + 'THE BUSTLE IN A HOUSE', 'THE MORNING AFTER DEATH', + 'IS SOLEMNEST OF INDUSTRIES', "ENACTED UPON EARTH,—", '', + "THE SWEEPING UP THE HEART,", 'AND PUTTING LOVE AWAY', + 'WE SHALL NOT WANT TO USE AGAIN', 'UNTIL ETERNITY.' + ]) run([BUSTLE_OUT], ['--decode'], expected) diff --git a/testing.py b/testing.py new file mode 100755 index 0000000..105363a --- /dev/null +++ b/testing.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +""" +Author : Add your Name +Date : 2025-01-26 +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()