#!/usr/bin/env python3 # rot - encode/decode using rot13, rot18 or rot47 # Copyright (c) 2026 640kb.neocities.org # This script is licensed under the Blue Oak Model License 1.0.0. # https://blueoakcouncil.org/license/1.0.0 # # 05aug2026: Initial release import sys import argparse if sys.version_info < (3, 6): sys.exit("Error: This script requires Python 3.6 or newer.") def show_help(): print(""" rot - encode/decode using rot13, rot18 or rot47. The script can read from a file or from standard input (stdin) - ideal for pipelines. USAGE: rot -r13 [file] ROT13 (rotates letters by 13 positions) rot -r18 [file] ROT18 (ROT13 on letters + ROT5 on digits) rot -r47 [file] ROT47 (rotates letters, numbers and punctuation) rot -h | --help Show this help EXAMPLES: # encode/decode a file to stdout rot -r13 file.txt rot -r13 file.txt > out.txt # read from stdin (to stdout) rot -r18 < file.txt rot -r18 < file.txt > out.txt # read from stdin via pipe (outputs to stdout) cat file.txt | rot -r47 cat file.txt | rot -r47 > out.txt # read as stdin from finger, outputs encode/decode to stdout finger user@domain.com | rot -r13 """) def rot13(text): result = [] for char in text: if 'a' <= char <= 'z': result.append(chr((ord(char) - ord('a') + 13) % 26 + ord('a'))) elif 'A' <= char <= 'Z': result.append(chr((ord(char) - ord('A') + 13) % 26 + ord('A'))) else: result.append(char) return ''.join(result) def rot18(text): result = [] for char in text: if 'a' <= char <= 'z': result.append(chr((ord(char) - ord('a') + 13) % 26 + ord('a'))) elif 'A' <= char <= 'Z': result.append(chr((ord(char) - ord('A') + 13) % 26 + ord('A'))) elif '0' <= char <= '9': result.append(chr((ord(char) - ord('0') + 5) % 10 + ord('0'))) else: result.append(char) return ''.join(result) def rot47(text): result = [] for char in text: if 33 <= ord(char) <= 126: result.append(chr(33 + ((ord(char) - 33 + 47) % 94))) else: result.append(char) return ''.join(result) def main(): if len(sys.argv) == 1 or sys.argv[1] in ("-h", "--help"): show_help() sys.exit(0) parser = argparse.ArgumentParser(description='ROT cipher encoder/decoder', add_help=False) parser.add_argument('file', nargs='?', help='Input file (optional, reads from stdin if not provided)') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-r13', action='store_true', help='ROT13 encode/decode') group.add_argument('-r18', action='store_true', help='ROT18 encode/decode') group.add_argument('-r47', action='store_true', help='ROT47 encode/decode') args = parser.parse_args() # Read input from file or stdin if args.file: try: with open(args.file, 'r', encoding='utf-8') as f: text = f.read() except FileNotFoundError: print(f"Error: File '{args.file}' not found", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Error reading file: {e}", file=sys.stderr) sys.exit(1) else: # Check if stdin is a terminal (no piped input) if sys.stdin.isatty(): show_help() sys.exit(1) text = sys.stdin.read() if args.r13: output = rot13(text) elif args.r18: output = rot18(text) elif args.r47: output = rot47(text) sys.stdout.write(output) if __name__ == '__main__': main()