File: def.py 1 #!/usr/bin/python3 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2024 pacman64 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the “Software”), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be included in 15 # all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 # SOFTWARE. 24 25 26 from socket import socket, timeout, AF_INET, SOCK_STREAM 27 from sys import argv, exit, stderr 28 from typing import Tuple 29 30 31 info = ''' 32 def [words...] 33 34 Lookup words from an online english dictionary at https://dict.org 35 ''' 36 37 # no args or a leading help-option arg means show the help message and quit 38 if len(argv) == 1 or argv[1] in ('-h', '--h', '-help', '--help'): 39 print(info.strip(), file=stderr) 40 exit(0) 41 42 43 def slurp(conn: socket, chunksize: int = 1024) -> Tuple[bytes, bool]: 44 'Read the whole dict-format response to a previously-sent query.' 45 46 chunks = [] 47 while True: 48 try: 49 chunk = conn.recv(chunksize) 50 except timeout: 51 return bytes().join(chunks), True 52 except Exception as e: 53 return bytes(str(e)), False 54 55 chunks.append(chunk) 56 if not chunk or b'\n221 bye' in chunk: 57 return bytes().join(chunks), True 58 59 60 def lookup(what: str, timeout: float = 0.25) -> Tuple[str, bool]: 61 'Handle the whole request-response process for a word lookup.' 62 63 conn = socket(AF_INET, SOCK_STREAM) 64 conn.connect(('dict.org', 2628)) 65 conn.sendall(bytes(f'define wn {what}\r\n', 'utf-8')) 66 conn.settimeout(timeout) 67 68 try: 69 resp, ok = slurp(conn) 70 s = resp.decode('utf-8').replace('\r\n', '\n') 71 return s, ok 72 except Exception: 73 return '', False 74 finally: 75 conn.close() 76 77 78 def show(s: str) -> bool: 79 ''' 80 Show response lines by color-coding them. This func returns false when 81 any error-like metadata lines are detected. 82 ''' 83 84 ok = True 85 86 for line in s.split('\n'): 87 if line == '': 88 print() 89 continue 90 91 # metadata lines start with a numeric digit 92 first = line[0] 93 94 # prevent looked-up numbers from showing as metadata 95 if ' ' not in line: 96 print(line) 97 continue 98 99 # code 151 has the specific source for the match, so 100 # use a unique style for that kind of metadata line 101 if line.startswith('151 '): 102 print('\x1b[38;5;4m', end='') 103 print(line, end='') 104 print('\x1b[0m') 105 continue 106 107 # gray out ok-type metadata lines 108 if first == '1' or first == '2' or line == '.': 109 print('\x1b[38;5;244m', end='') 110 print(line, end='') 111 print('\x1b[0m') 112 continue 113 114 # make error-type metadata lines stand out 115 if first == '3' or first == '4' or first == '5': 116 ok = False 117 print('\x1b[38;5;124m', end='') 118 print(line, end='') 119 print('\x1b[0m') 120 continue 121 122 # keep all other lines unstyled 123 print(line) 124 125 return ok 126 127 128 def help() -> None: 129 'Show a help message.' 130 131 out = stderr 132 print('def [words...]', file=out) 133 print(file=out) 134 msg = 'Lookup words from an online english dictionary at https://dict.org' 135 print(msg, file=out) 136 137 138 # just show a help message when given no words to lookup 139 if len(argv) < 2: 140 help() 141 exit(0) 142 143 # handle explicit help options 144 if len(argv) > 1 and argv[1] in ('-h', '-help', '--h', '--help'): 145 help() 146 exit(0) 147 148 # don't show a styled title line when given only 1 word to lookup 149 if len(argv) == 2: 150 res, ok1 = lookup(argv[1]) 151 ok2 = show(res) 152 exit(0 if ok1 and ok2 else 1) 153 154 # handle multiple words to lookup by preceding each with styled title lines 155 nerr = 0 156 for i, word in enumerate(argv[1:]): 157 if i > 0: 158 print() 159 print(f'\x1b[7m{word:80}\x1b[0m') 160 (res, ok1) = lookup(word) 161 ok2 = show(res) 162 if not ok1 or not ok2: 163 nerr += 1 164 165 # fail the script if any lookup failed 166 if nerr > 0: 167 exit(1)