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 # def [words...] 27 # 28 # Lookup words from an online english dictionary at https://dict.org 29 30 31 from socket import socket, timeout, AF_INET, SOCK_STREAM 32 from sys import argv, exit, stderr, stdout 33 34 35 def slurp(conn: socket, chunksize: int = 1024) -> (bytes, bool): 36 '''Read the whole dict-format response to a previously-sent query.''' 37 38 chunks = [] 39 while True: 40 try: 41 chunk = conn.recv(chunksize) 42 except timeout: 43 return bytes().join(chunks), True 44 except Exception as e: 45 return bytes(str(e)), False 46 47 chunks.append(chunk) 48 if not chunk or b'\n221 bye' in chunk: 49 return bytes().join(chunks), True 50 51 52 def lookup(what: str, timeout: float = 0.25) -> (str, bool): 53 '''Handle the whole request-response process for a word lookup.''' 54 55 conn = socket(AF_INET, SOCK_STREAM) 56 conn.connect(('dict.org', 2628)) 57 conn.sendall(bytes(f'define wn {what}\r\n', 'utf-8')) 58 conn.settimeout(timeout) 59 60 try: 61 resp, ok = slurp(conn) 62 s = resp.decode('utf-8').replace('\r\n', '\n') 63 return s, ok 64 except: 65 return '', False 66 finally: 67 conn.close() 68 69 70 def show(s: str) -> bool: 71 ''' 72 Show response lines by color-coding them. This func returns false when 73 any error-like metadata lines are detected. 74 ''' 75 76 ok = True 77 78 for line in s.split('\n'): 79 if line == '': 80 print() 81 continue 82 83 # metadata lines start with a numeric digit 84 first = line[0] 85 86 # prevent looked-up numbers from showing as metadata 87 if ' ' not in line: 88 print(line) 89 continue 90 91 # code 151 has the specific source for the match, so 92 # use a unique style for that kind of metadata line 93 if line.startswith('151 '): 94 print('\x1b[38;5;4m', end='') 95 print(line, end='') 96 print('\x1b[0m') 97 continue 98 99 # gray out ok-type metadata lines 100 if first == '1' or first == '2' or line == '.': 101 print('\x1b[38;5;244m', end='') 102 print(line, end='') 103 print('\x1b[0m') 104 continue 105 106 # make error-type metadata lines stand out 107 if first == '3' or first == '4' or first == '5': 108 ok = False 109 print('\x1b[38;5;124m', end='') 110 print(line, end='') 111 print('\x1b[0m') 112 continue 113 114 # keep all other lines unstyled 115 print(line) 116 117 return ok 118 119 120 def help() -> None: 121 '''Show a help message.''' 122 123 out = stderr 124 print('def [words...]', file=out) 125 print(file=out) 126 msg = 'Lookup words from an online english dictionary at https://dict.org' 127 print(msg, file=out) 128 129 130 # just show a help message when given no words to lookup 131 if len(argv) < 2: 132 help() 133 exit(0) 134 135 # handle explicit help options 136 if len(argv) > 1 and argv[1] in ('-h', '-help', '--h', '--help'): 137 help() 138 exit(0) 139 140 stdout.reconfigure(newline='\n', encoding='utf-8') 141 142 # don't show a styled title line when given only 1 word to lookup 143 if len(argv) == 2: 144 res, ok1 = lookup(argv[1]) 145 ok2 = show(res) 146 exit(0 if ok1 and ok2 else 1) 147 148 # handle multiple words to lookup by preceding each with styled title lines 149 nerr = 0 150 for i, word in enumerate(argv[1:]): 151 if i > 0: 152 print() 153 print(f'\x1b[7m{word:80}\x1b[0m') 154 (res, ok1) = lookup(word) 155 ok2 = show(res) 156 if not ok1 or not ok2: 157 nerr += 1 158 159 # fail the script if any lookup failed 160 if nerr > 0: 161 exit(1)