phrases.py (11563B)
1 """Jeff's Phrasing dictionary integration for stenography practice. 2 3 Loads the Jeff's Phrasing Plover dictionary plugin and provides functions 4 to generate phrasing-based practice content. 5 """ 6 7 import os 8 import random 9 import sys 10 11 # --------------------------------------------------------------------------- 12 # Import Jeff's Phrasing dictionary 13 # --------------------------------------------------------------------------- 14 15 JEFF_PHRASING = None 16 17 _jeff_phrasing_dir = os.path.expanduser( 18 "~/.config/plover/dicts/jeff-phrasing" 19 ) 20 21 try: 22 if _jeff_phrasing_dir not in sys.path: 23 sys.path.insert(0, _jeff_phrasing_dir) 24 import importlib 25 JEFF_PHRASING = importlib.import_module("jeff-phrasing") 26 except Exception: 27 JEFF_PHRASING = None 28 29 # --------------------------------------------------------------------------- 30 # Starters and enders used to generate phrase combinations 31 # --------------------------------------------------------------------------- 32 33 # Pronoun starters: stroke -> display word 34 STARTER_STROKES = { 35 "SWR": "I", 36 "KPWR": "you", 37 "KWHR": "he", 38 "SKWHR": "she", 39 "KPWH": "it", 40 "TWR": "we", 41 "TWH": "they", 42 } 43 44 # Common ender strokes (present tense only for generation) 45 ENDER_STROKES = { 46 "RB": "ask", 47 "B": "be", 48 "RPBG": "become", 49 "BL": "believe", 50 "RBLG": "call", 51 "BGS": "can", 52 "RZ": "care", 53 "PBGZ": "change", 54 "BG": "come", 55 "RP": "do", 56 "PGS": "expect", 57 "LT": "feel", 58 "PBLG": "find", 59 "RG": "forget", 60 "GS": "get", 61 "GZ": "give", 62 "G": "go", 63 "GT": "go to", 64 "T": "have", 65 "TS": "have to", 66 "PZ": "happen", 67 "PG": "hear", 68 "RPS": "hope", 69 "PLG": "imagine", 70 "PBGS": "keep", 71 "PB": "know", 72 "RPBS": "learn", 73 "LS": "let", 74 "BLG": "like", 75 "LZ": "live", 76 "L": "look", 77 "LG": "love", 78 "RPBL": "make", 79 "PL": "may", 80 "PBL": "mean", 81 "PLZ": "move", 82 "PBLGS": "must", 83 "RPG": "need", 84 "RPGT": "need to", 85 "PS": "put", 86 "RPL": "remember", 87 "R": "run", 88 "BS": "say", 89 "S": "see", 90 "PLS": "seem", 91 "RBL": "shall", 92 "RBGS": "will", 93 "RBGSZ": "would", 94 "RBS": "wish", 95 "RBG": "work", 96 "RBT": "take", 97 "RLT": "tell", 98 "PBG": "think", 99 "RT": "try", 100 "RPB": "understand", 101 "Z": "use", 102 "P": "want", 103 "PT": "want to", 104 } 105 106 # Middle/structure modifiers to combine with starters + enders 107 MIDDLE_STROKES = { 108 "": "simple present/past", 109 "*": "negative", 110 "A": "can/could", 111 "A*": "can't/couldn't", 112 "O": "shall/should", 113 "AO": "will/would", 114 "AO*": "won't/wouldn't", 115 } 116 117 # --------------------------------------------------------------------------- 118 # Phrase generation via Jeff's Phrasing lookup 119 # --------------------------------------------------------------------------- 120 121 def _try_lookup(stroke): 122 """Attempt to look up a single stroke via Jeff's Phrasing. Returns text or None.""" 123 if JEFF_PHRASING is None: 124 return None 125 try: 126 return JEFF_PHRASING.lookup((stroke,)).strip() 127 except (KeyError, TypeError, AttributeError): 128 return None 129 130 131 def _build_stroke(starter, middle, ender): 132 """Combine starter, middle, and ender stroke fragments into a single stroke string.""" 133 import re 134 hyphen_chars = re.compile(r"[AO*EU-]") 135 136 combined = starter + middle + ender 137 138 # Add hyphen between left and right bank when needed 139 if not hyphen_chars.search(starter + middle) and ender: 140 combined = starter + middle + "-" + ender 141 142 return combined 143 144 145 def _generate_phrase_list(): 146 """Build a list of (phrase_text, stroke) tuples from starter + ender combinations.""" 147 if JEFF_PHRASING is None: 148 return [] 149 150 phrases = [] 151 seen = set() 152 153 # Simple starter + ender (present tense) 154 for s_stroke in STARTER_STROKES: 155 for e_stroke in ENDER_STROKES: 156 stroke = _build_stroke(s_stroke, "", e_stroke) 157 text = _try_lookup(stroke) 158 if text and text not in seen: 159 phrases.append((text, stroke)) 160 seen.add(text) 161 162 # Starter + middle + ender 163 for s_stroke in STARTER_STROKES: 164 for m_stroke in MIDDLE_STROKES: 165 if not m_stroke: 166 continue 167 for e_stroke in ENDER_STROKES: 168 stroke = _build_stroke(s_stroke, m_stroke, e_stroke) 169 text = _try_lookup(stroke) 170 if text and text not in seen: 171 phrases.append((text, stroke)) 172 seen.add(text) 173 174 # Past tense: starter + ender with D suffix 175 for s_stroke in STARTER_STROKES: 176 for e_stroke, verb in ENDER_STROKES.items(): 177 past_stroke = e_stroke + "D" if not e_stroke.endswith("D") else e_stroke 178 stroke = _build_stroke(s_stroke, "", past_stroke) 179 text = _try_lookup(stroke) 180 if text and text not in seen: 181 phrases.append((text, stroke)) 182 seen.add(text) 183 184 # Structure variations: be/have forms 185 structure_mods = ["E", "F", "EF"] # be, have, have been 186 for s_stroke in STARTER_STROKES: 187 for struct in structure_mods: 188 for e_stroke in ENDER_STROKES: 189 stroke = _build_stroke(s_stroke, struct, e_stroke) 190 text = _try_lookup(stroke) 191 if text and text not in seen: 192 phrases.append((text, stroke)) 193 seen.add(text) 194 195 return phrases 196 197 198 # --------------------------------------------------------------------------- 199 # Pre-computed phrase cache 200 # --------------------------------------------------------------------------- 201 202 _PHRASE_CACHE = [] 203 _PHRASE_TEXT_SET = set() 204 205 206 def _init_cache(): 207 global _PHRASE_CACHE, _PHRASE_TEXT_SET 208 if not _PHRASE_CACHE: 209 _PHRASE_CACHE = _generate_phrase_list() 210 _PHRASE_TEXT_SET = {p[0] for p in _PHRASE_CACHE} 211 212 213 _init_cache() 214 215 # --------------------------------------------------------------------------- 216 # Public API 217 # --------------------------------------------------------------------------- 218 219 def get_phrase_strokes(phrase_text): 220 """Return a list of stroke tuples for the given phrase text using Jeff's reverse_lookup. 221 222 Example return: [("SWRPB",), ("SWR-PB",)] 223 Returns an empty list if Jeff's Phrasing is not available or the phrase is not found. 224 """ 225 if JEFF_PHRASING is None: 226 return [] 227 try: 228 return JEFF_PHRASING.reverse_lookup(phrase_text) 229 except Exception: 230 return [] 231 232 233 def get_practice_phrases(count=20): 234 """Return a list of random phrase texts for practice. 235 236 Each item is a dict with 'text' and 'strokes' keys. 237 """ 238 if not _PHRASE_CACHE: 239 return [{"text": s, "strokes": []} for s in random.sample( 240 CURATED_SENTENCES, min(count, len(CURATED_SENTENCES)) 241 )] 242 243 phrases = random.sample(_PHRASE_CACHE, min(count, len(_PHRASE_CACHE))) 244 return [{"text": text, "strokes": get_phrase_strokes(text)} for text, stroke in phrases] 245 246 247 def get_all_cached_phrases(): 248 """Return the full list of pre-computed (phrase_text, stroke) tuples.""" 249 return list(_PHRASE_CACHE) 250 251 252 def get_sentence_words(count=10): 253 """Generate simple practice sentences that combine phrases with common words. 254 255 Returns a list of sentence strings suitable for steno practice. 256 """ 257 sentences = list(CURATED_SENTENCES) 258 random.shuffle(sentences) 259 260 if count <= len(sentences): 261 return sentences[:count] 262 263 # If we need more than curated, also generate from phrase combinations 264 generated = [] 265 completions = [ 266 "now", "here", "today", "again", "soon", "there", 267 "at home", "at work", "in the morning", "every day", 268 "to the store", "for a while", "right now", "this time", 269 "a lot", "so much", "very well", "too much", 270 "about it", "with you", "for me", "to them", 271 ] 272 273 if _PHRASE_CACHE: 274 for text, stroke in random.sample(_PHRASE_CACHE, min(50, len(_PHRASE_CACHE))): 275 completion = random.choice(completions) 276 generated.append(f"{text} {completion}") 277 278 combined = sentences + generated 279 random.shuffle(combined) 280 return combined[:count] 281 282 283 # --------------------------------------------------------------------------- 284 # Hand-curated practice sentences 285 # --------------------------------------------------------------------------- 286 287 CURATED_SENTENCES = [ 288 # Short phrases (2-4 words) 289 "I can do it", 290 "you have to go", 291 "I want to know", 292 "she will come", 293 "they have to work", 294 "we can see it", 295 "he will go", 296 "I need to go", 297 "you can do it", 298 "it will be", 299 "I have to say", 300 "they want to come", 301 "she can go", 302 "we need to talk", 303 "he has to go", 304 "I would like to", 305 "you should go", 306 "they will come", 307 "we have to go", 308 "I can tell", 309 "she will be", 310 "you have to come", 311 "it can be", 312 "he will come", 313 "I should go", 314 315 # Medium phrases (4-6 words) 316 "they don't want to come", 317 "she has been working", 318 "I don't think so", 319 "you can't do that", 320 "we have been here", 321 "he doesn't want to go", 322 "I have been working", 323 "they will have to come", 324 "she can't believe it", 325 "you don't have to go", 326 "we should try to go", 327 "I don't want to go", 328 "he has been looking", 329 "they don't know how", 330 "you should have come", 331 "I can't believe it", 332 "she doesn't want to come", 333 "we don't have to go", 334 "he will have to come", 335 "I have to tell you", 336 "they can't come today", 337 "you don't need to go", 338 "she would like to come", 339 "we will have to go", 340 "it doesn't have to be", 341 "I used to live here", 342 "you have to believe me", 343 "they have been working", 344 "she will need to go", 345 "he can't seem to find", 346 347 # Longer sentences (6-10 words) 348 "I think that you should go to the store", 349 "they have been working on it all day", 350 "she will come to the house in the morning", 351 "we don't want to go to work today", 352 "he has been trying to find a new home", 353 "I would like to tell you about it", 354 "you should try to come here every day", 355 "they don't believe that it will work", 356 "she has to go to the store today", 357 "we need to find a way to do it", 358 "I can't remember how to get there", 359 "you will have to come back again soon", 360 "they should have been here by now", 361 "he doesn't seem to care about it", 362 "I have been trying to call you all day", 363 "she will need to learn how to do it", 364 "we can't go to the store right now", 365 "you don't have to do it today", 366 "they used to live in a big house", 367 "I would like to know what you think", 368 "he can't seem to make it work", 369 "she has been wanting to come here", 370 "we should try to get there on time", 371 "you have to tell me what you know", 372 "they will need to come back tomorrow", 373 374 # Common conversational phrases 375 "I don't know", 376 "you have to", 377 "I want to", 378 "she will be there", 379 "they have been", 380 "we can do it", 381 "he doesn't know", 382 "I will try", 383 "you should know", 384 "it has to be", 385 "I can see", 386 "they don't care", 387 "we will go", 388 "she has been", 389 "he will try", 390 "I have been here before", 391 "you can come with me", 392 "they should have told us", 393 "she would like to help", 394 "we need to go home now", 395 "he used to work here", 396 "I will have to think about it", 397 "you don't seem to understand", 398 "they can't find it", 399 "she has to come back", 400 ]