kaqing 2.0.171__py3-none-any.whl → 2.0.203__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. adam/app_session.py +5 -10
  2. adam/apps.py +18 -4
  3. adam/batch.py +7 -7
  4. adam/checks/check_utils.py +3 -1
  5. adam/checks/disk.py +2 -3
  6. adam/columns/memory.py +3 -4
  7. adam/commands/__init__.py +15 -6
  8. adam/commands/alter_tables.py +26 -41
  9. adam/commands/app/__init__.py +0 -0
  10. adam/commands/{app_cmd.py → app/app.py} +2 -2
  11. adam/commands/{show → app}/show_app_actions.py +7 -15
  12. adam/commands/{show → app}/show_app_queues.py +1 -4
  13. adam/{utils_app.py → commands/app/utils_app.py} +9 -1
  14. adam/commands/audit/audit.py +9 -26
  15. adam/commands/audit/audit_repair_tables.py +5 -7
  16. adam/commands/audit/audit_run.py +1 -1
  17. adam/commands/audit/completions_l.py +15 -0
  18. adam/commands/audit/show_last10.py +2 -14
  19. adam/commands/audit/show_slow10.py +2 -13
  20. adam/commands/audit/show_top10.py +2 -11
  21. adam/commands/audit/utils_show_top10.py +15 -3
  22. adam/commands/bash/bash.py +1 -1
  23. adam/commands/bash/utils_bash.py +1 -1
  24. adam/commands/cassandra/__init__.py +0 -0
  25. adam/commands/cassandra/download_cassandra_log.py +45 -0
  26. adam/commands/cassandra/nodetool.py +64 -0
  27. adam/commands/cassandra/nodetool_commands.py +120 -0
  28. adam/commands/cassandra/restart_cluster.py +47 -0
  29. adam/commands/cassandra/restart_node.py +51 -0
  30. adam/commands/cassandra/restart_nodes.py +47 -0
  31. adam/commands/cassandra/rollout.py +88 -0
  32. adam/commands/cat.py +5 -19
  33. adam/commands/cd.py +7 -9
  34. adam/commands/check.py +10 -18
  35. adam/commands/cli_commands.py +6 -1
  36. adam/commands/{cp.py → clipboard_copy.py} +34 -36
  37. adam/commands/code.py +2 -2
  38. adam/commands/command.py +139 -22
  39. adam/commands/commands_utils.py +14 -12
  40. adam/commands/cql/alter_tables.py +66 -0
  41. adam/commands/cql/completions_c.py +29 -0
  42. adam/commands/cql/cqlsh.py +3 -7
  43. adam/commands/cql/utils_cql.py +23 -61
  44. adam/commands/debug/__init__.py +0 -0
  45. adam/commands/debug/debug.py +22 -0
  46. adam/commands/debug/debug_completes.py +35 -0
  47. adam/commands/debug/debug_timings.py +35 -0
  48. adam/commands/deploy/deploy_pg_agent.py +2 -2
  49. adam/commands/deploy/deploy_pod.py +2 -4
  50. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  51. adam/commands/devices/device.py +40 -9
  52. adam/commands/devices/device_app.py +19 -29
  53. adam/commands/devices/device_auit_log.py +3 -3
  54. adam/commands/devices/device_cass.py +17 -23
  55. adam/commands/devices/device_export.py +12 -11
  56. adam/commands/devices/device_postgres.py +79 -63
  57. adam/commands/devices/devices.py +1 -1
  58. adam/commands/download_cassandra_log.py +45 -0
  59. adam/commands/download_file.py +47 -0
  60. adam/commands/export/clean_up_all_export_sessions.py +3 -3
  61. adam/commands/export/clean_up_export_sessions.py +7 -19
  62. adam/commands/export/completions_x.py +11 -0
  63. adam/commands/export/download_export_session.py +40 -0
  64. adam/commands/export/drop_export_database.py +6 -22
  65. adam/commands/export/drop_export_databases.py +3 -9
  66. adam/commands/export/export.py +1 -17
  67. adam/commands/export/export_databases.py +109 -32
  68. adam/commands/export/export_select.py +8 -55
  69. adam/commands/export/export_sessions.py +211 -0
  70. adam/commands/export/export_use.py +13 -16
  71. adam/commands/export/export_x_select.py +48 -0
  72. adam/commands/export/exporter.py +176 -167
  73. adam/commands/export/import_files.py +44 -0
  74. adam/commands/export/import_session.py +10 -6
  75. adam/commands/export/importer.py +24 -9
  76. adam/commands/export/importer_athena.py +114 -44
  77. adam/commands/export/importer_sqlite.py +45 -23
  78. adam/commands/export/show_column_counts.py +11 -20
  79. adam/commands/export/show_export_databases.py +5 -2
  80. adam/commands/export/show_export_session.py +6 -15
  81. adam/commands/export/show_export_sessions.py +4 -11
  82. adam/commands/export/utils_export.py +79 -27
  83. adam/commands/find_files.py +51 -0
  84. adam/commands/find_processes.py +76 -0
  85. adam/commands/generate_report.py +52 -0
  86. adam/commands/head.py +36 -0
  87. adam/commands/help.py +2 -2
  88. adam/commands/intermediate_command.py +6 -3
  89. adam/commands/login.py +3 -6
  90. adam/commands/ls.py +2 -2
  91. adam/commands/medusa/medusa_backup.py +13 -16
  92. adam/commands/medusa/medusa_restore.py +26 -37
  93. adam/commands/medusa/medusa_show_backupjobs.py +7 -7
  94. adam/commands/medusa/medusa_show_restorejobs.py +6 -6
  95. adam/commands/medusa/utils_medusa.py +15 -0
  96. adam/commands/nodetool.py +3 -8
  97. adam/commands/os/__init__.py +0 -0
  98. adam/commands/os/cat.py +36 -0
  99. adam/commands/os/download_file.py +47 -0
  100. adam/commands/os/find_files.py +51 -0
  101. adam/commands/os/find_processes.py +76 -0
  102. adam/commands/os/head.py +36 -0
  103. adam/commands/os/shell.py +41 -0
  104. adam/commands/param_get.py +10 -12
  105. adam/commands/param_set.py +7 -10
  106. adam/commands/postgres/completions_p.py +22 -0
  107. adam/commands/postgres/postgres.py +25 -40
  108. adam/commands/postgres/postgres_databases.py +269 -0
  109. adam/commands/postgres/utils_postgres.py +33 -20
  110. adam/commands/preview_table.py +4 -2
  111. adam/commands/pwd.py +4 -6
  112. adam/commands/reaper/reaper_forward.py +2 -2
  113. adam/commands/reaper/reaper_run_abort.py +4 -10
  114. adam/commands/reaper/reaper_runs.py +3 -3
  115. adam/commands/reaper/reaper_schedule_activate.py +12 -12
  116. adam/commands/reaper/reaper_schedule_start.py +7 -12
  117. adam/commands/reaper/reaper_schedule_stop.py +7 -12
  118. adam/commands/reaper/utils_reaper.py +13 -6
  119. adam/commands/repair/repair_log.py +1 -4
  120. adam/commands/repair/repair_run.py +3 -8
  121. adam/commands/repair/repair_scan.py +1 -6
  122. adam/commands/repair/repair_stop.py +1 -5
  123. adam/commands/restart_cluster.py +47 -0
  124. adam/commands/restart_node.py +51 -0
  125. adam/commands/restart_nodes.py +47 -0
  126. adam/commands/shell.py +9 -2
  127. adam/commands/show/show.py +4 -4
  128. adam/commands/show/show_adam.py +3 -3
  129. adam/commands/show/show_cassandra_repairs.py +5 -6
  130. adam/commands/show/show_cassandra_status.py +29 -29
  131. adam/commands/show/show_cassandra_version.py +1 -4
  132. adam/commands/show/{show_commands.py → show_cli_commands.py} +3 -6
  133. adam/commands/show/show_login.py +3 -9
  134. adam/commands/show/show_params.py +2 -5
  135. adam/commands/show/show_processes.py +15 -16
  136. adam/commands/show/show_storage.py +9 -8
  137. adam/config.py +4 -5
  138. adam/embedded_params.py +1 -1
  139. adam/log.py +4 -4
  140. adam/repl.py +26 -18
  141. adam/repl_commands.py +32 -20
  142. adam/repl_session.py +9 -1
  143. adam/repl_state.py +39 -10
  144. adam/sql/async_executor.py +44 -0
  145. adam/sql/lark_completer.py +286 -0
  146. adam/sql/lark_parser.py +604 -0
  147. adam/sql/qingl.lark +1076 -0
  148. adam/sql/sql_completer.py +4 -6
  149. adam/sql/sql_state_machine.py +25 -13
  150. adam/sso/authn_ad.py +2 -5
  151. adam/sso/authn_okta.py +2 -4
  152. adam/sso/cred_cache.py +2 -5
  153. adam/sso/idp.py +8 -11
  154. adam/utils.py +299 -105
  155. adam/utils_athena.py +18 -18
  156. adam/utils_audits.py +3 -7
  157. adam/utils_issues.py +2 -2
  158. adam/utils_k8s/app_clusters.py +4 -4
  159. adam/utils_k8s/app_pods.py +8 -6
  160. adam/utils_k8s/cassandra_clusters.py +16 -5
  161. adam/utils_k8s/cassandra_nodes.py +9 -6
  162. adam/utils_k8s/custom_resources.py +11 -17
  163. adam/utils_k8s/jobs.py +7 -11
  164. adam/utils_k8s/k8s.py +14 -5
  165. adam/utils_k8s/kube_context.py +3 -6
  166. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +4 -4
  167. adam/utils_k8s/pods.py +85 -23
  168. adam/utils_k8s/statefulsets.py +5 -2
  169. adam/utils_local.py +42 -0
  170. adam/utils_repl/appendable_completer.py +6 -0
  171. adam/utils_repl/repl_completer.py +45 -2
  172. adam/utils_repl/state_machine.py +3 -3
  173. adam/utils_sqlite.py +58 -30
  174. adam/version.py +1 -1
  175. {kaqing-2.0.171.dist-info → kaqing-2.0.203.dist-info}/METADATA +1 -1
  176. kaqing-2.0.203.dist-info/RECORD +277 -0
  177. kaqing-2.0.203.dist-info/top_level.txt +2 -0
  178. teddy/__init__.py +0 -0
  179. teddy/lark_parser.py +436 -0
  180. teddy/lark_parser2.py +618 -0
  181. adam/commands/cql/cql_completions.py +0 -33
  182. adam/commands/export/export_handlers.py +0 -71
  183. adam/commands/export/export_select_x.py +0 -54
  184. adam/commands/logs.py +0 -37
  185. adam/commands/postgres/postgres_context.py +0 -274
  186. adam/commands/postgres/psql_completions.py +0 -10
  187. adam/commands/report.py +0 -61
  188. adam/commands/restart.py +0 -60
  189. kaqing-2.0.171.dist-info/RECORD +0 -236
  190. kaqing-2.0.171.dist-info/top_level.txt +0 -1
  191. /adam/commands/{app_ping.py → app/app_ping.py} +0 -0
  192. /adam/commands/{show → app}/show_app_id.py +0 -0
  193. {kaqing-2.0.171.dist-info → kaqing-2.0.203.dist-info}/WHEEL +0 -0
  194. {kaqing-2.0.171.dist-info → kaqing-2.0.203.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,604 @@
1
+ from copy import copy
2
+ import re
3
+ from typing import Union, cast
4
+ from lark import Lark, Token, Tree
5
+ from lark.lexer import TerminalDef
6
+ from lark.grammar import NonTerminal, Terminal
7
+
8
+ def log(msg: str = None, debug: bool = False, depth: int = 0):
9
+ if not msg:
10
+ print()
11
+ return
12
+
13
+ if debug:
14
+ print(depth * ' ' + msg)
15
+
16
+ class GNode:
17
+ def __init__(self, name: str, token: Token = None, choices: list[int] = []):
18
+ self.name = name
19
+ self.token = token
20
+ self.choices = choices
21
+
22
+ def __eq__(self, other: 'GNode'):
23
+ return self.__repr__() == other.__repr__()
24
+
25
+ def __hash__(self):
26
+ return hash(self.__repr__())
27
+
28
+ def __repr__(self):
29
+ n = self.name if self.name else f'{self.token}'
30
+ if self.choices:
31
+ n = f'{n}-{"-".join([f"{c}" for c in self.choices])}'
32
+
33
+ return n
34
+
35
+ def pname(self):
36
+ return self.name if self.name else f'{self.token}'
37
+
38
+ def choice(self, depth: int):
39
+ if depth < len(self.choices):
40
+ return self.choices[depth]
41
+
42
+ return -1
43
+
44
+ def drop_last_choice(self):
45
+ new_node = copy(self)
46
+ new_node.choices = self.choices[:-1]
47
+
48
+ return new_node
49
+
50
+ def parse(s: str):
51
+ name_n_choices = s.split('-')
52
+ choices = []
53
+ if len(name_n_choices) > 1:
54
+ choices = {int(k) for k in name_n_choices[1:]}
55
+
56
+ return GNode(name_n_choices[0], choices=choices)
57
+
58
+ def add_choice(self, choice: int):
59
+ self.choices.append(choice)
60
+
61
+ class GPath:
62
+ def __init__(self, nodes: list[GNode], complete: bool):
63
+ self.nodes = nodes
64
+ self.complete = complete
65
+
66
+ def __eq__(self, other: 'GPath'):
67
+ return self.__repr__() == other.__repr__()
68
+
69
+ def __hash__(self):
70
+ return hash(self.__repr__())
71
+
72
+ def __repr__(self):
73
+ r = '.'.join([f'{p}' for p in self.nodes])
74
+
75
+ return r
76
+
77
+ def token(self):
78
+ return self.nodes[-1] if self.nodes else GNode(None, choices={})
79
+
80
+ def append(self, node):
81
+ new_path = copy(self)
82
+ nodes = []
83
+ for n in self.nodes:
84
+ n1 = copy(n)
85
+ ss = []
86
+ for k in n.choices:
87
+ ss.append(k)
88
+ n1.choices = ss
89
+ nodes.append(n1)
90
+ nodes.append(node)
91
+ new_path.nodes = nodes
92
+
93
+ return new_path
94
+
95
+ def drop_last(self):
96
+ new_path = copy(self)
97
+ nodes = []
98
+ for n in self.nodes[:-1]:
99
+ nodes.append(n)
100
+
101
+ new_path.nodes = nodes
102
+
103
+ return new_path
104
+
105
+ def clone(self):
106
+ new_path = copy(self)
107
+ nodes = []
108
+ for n in self.nodes:
109
+ n1 = copy(n)
110
+ ss = []
111
+ for k in n.choices:
112
+ ss.append(k)
113
+ n1.choices = ss
114
+ nodes.append(n1)
115
+ new_path.nodes = nodes
116
+
117
+ return new_path
118
+
119
+ def terminal(self):
120
+ last_node = self.nodes[-1]
121
+ return last_node.pname()
122
+
123
+ def terminals(paths: set['GPath']):
124
+ return ','.join(p.terminal() for p in list(paths))
125
+
126
+ def with_new_choice(self, choice: int):
127
+ new_path = self.clone()
128
+ new_path.nodes[-1].add_choice(choice)
129
+
130
+ return new_path
131
+
132
+ def with_next_choice(self):
133
+ new_path = self.clone()
134
+ new_path.nodes[-1].choices[-1] += 1
135
+
136
+ return new_path
137
+
138
+ def drop_last_choice(self):
139
+ new_path = self.clone()
140
+ new_path.nodes[-1] = new_path.nodes[-1].drop_last_choice()
141
+
142
+ return new_path
143
+
144
+ def name_path(self):
145
+ # select_clause-0-2.projection-0-1-0-0.result_expr-1-0.expr-0-0-0-0-1-0.path-0-0.identifier_ref-0-0
146
+ return '.'.join([n.name for n in self.nodes])
147
+
148
+ class LarkParser:
149
+ show_returns = False
150
+
151
+ def __init__(self, grammar: str = None, contexts_by_path: dict[str, str] = {}):
152
+ if not grammar:
153
+ grammar = """
154
+ start: expression
155
+ expression: term (("+" | "-") term)*
156
+ term: factor (("*" | "/") factor)*
157
+ factor: NUMBER | "(" expression ")"
158
+ NUMBER: /[0-9]+/
159
+ %ignore " "
160
+ """
161
+
162
+ self.contexts_by_path = contexts_by_path
163
+
164
+ parser = Lark(grammar, start='start')
165
+ self.parser = parser
166
+
167
+ self.rules = {rule_def[0]: rule_def[2] for rule_def in parser.grammar.rule_defs}
168
+ self.terminals = {rule_def[0]: rule_def[1] for rule_def in parser.grammar.term_defs}
169
+ self.rules_by_name1 = {rule_def[0]: rule_def[1] for rule_def in parser.grammar.rule_defs}
170
+ self.rules_by_name2 = {rule_def[0]: rule_def[3] for rule_def in parser.grammar.rule_defs}
171
+
172
+ self.trees: dict[str, Tree] = {}
173
+ self._token_values_by_type: dict[str, str] = {}
174
+
175
+ def find_next_terminals(self, path: GPath, debug=False):
176
+ if path == 'start':
177
+ return self.visit_current(LarkParser.build_path(path), debug=debug)
178
+
179
+ if isinstance(path, set):
180
+ paths = set()
181
+
182
+ for p in list(path):
183
+ paths |= self.visit_next(p, debug=debug)
184
+
185
+ return paths
186
+ elif isinstance(path, str):
187
+ path = LarkParser.build_path(path)
188
+
189
+ if debug: print(f'find_next_terminals({path}, {path.token().token})')
190
+
191
+ return self.visit_next(path, debug=debug)
192
+
193
+ def visit_current(self, path: GPath, tree: Tree = None, debug=False):
194
+ paths: set[GPath] = set()
195
+
196
+ if debug: print(f'visit_current({path}, {path.token().token}), {path.nodes[-1].choices}')
197
+ if not tree:
198
+ tree, _ = self.tree_by_choices(path, debug=debug)
199
+
200
+ if isinstance(tree, Token):
201
+ if debug: print('token', tree)
202
+ if path.nodes[-1].token:
203
+ path = path.drop_last()
204
+
205
+ p = path.append(GNode(name=self.token_type_from_raw(f'{tree}'), token=tree, choices=[]))
206
+ if LarkParser.show_returns: print('<- ', tree.value, '\t\t', p)
207
+ paths.add(p)
208
+ elif isinstance(tree, Terminal):
209
+ if debug: print('terminal', tree.name)
210
+ p = path.append(GNode(tree.name, choices=[]))
211
+ if LarkParser.show_returns: print('<- ', tree.name, '\t\t', p)
212
+ paths.add(p)
213
+ elif isinstance(tree, NonTerminal):
214
+ if debug: print('non-termial', tree.name, tree)
215
+ paths |= self.visit_current(path.append(GNode(tree.name, choices=[])), debug=debug)
216
+ elif isinstance(tree, tuple):
217
+ paths |= self.visit_current(path, tree=tree[0], debug=debug)
218
+ elif not tree:
219
+ raise Exception(f'null tree at {path}')
220
+ elif tree.data == 'expr':
221
+ if debug: print('expr')
222
+ paths |= self.visit_current(path.with_new_choice(0), tree=tree.children[0], debug=debug)
223
+
224
+ if cast(Token, tree.children[1]).value in ['*', '?']:
225
+ paths |= self.visit_next(path, debug=debug)
226
+ elif tree.data == 'maybe':
227
+ if debug: print('maybe')
228
+
229
+ paths |= self.visit_current(path.with_new_choice(0), tree=tree.children[0], debug=debug)
230
+ paths |= self.visit_next(path, debug=debug)
231
+ elif tree.data == 'value':
232
+ if debug: print('value')
233
+ for child in tree.children:
234
+ paths |= self.visit_current(path, tree=child, debug=debug)
235
+ elif tree.data == 'range':
236
+ if debug: print('range')
237
+ token0: Token = tree.children[0]
238
+ token1: Token = tree.children[1]
239
+ if token0.value == '"0"' and token1.value == '"9"':
240
+ p = path.append(GNode(name=self.token_type_from_raw(f'{token0}'), token=token0, choices=[]))
241
+ if LarkParser.show_returns: print('<- ', token0, '\t\t', p)
242
+ paths.add(p)
243
+ else:
244
+ raise Exception('not implented')
245
+ elif tree.data == 'literal':
246
+ if debug: print('literal')
247
+ for child in tree.children:
248
+ paths |= self.visit_current(path, tree=child, debug=debug)
249
+ elif tree.data == 'expansions':
250
+ for i, child in enumerate(tree.children):
251
+ paths |= self.visit_current(path.with_new_choice(i), tree=child, debug=debug)
252
+ elif tree.data == 'expansion':
253
+ paths |= self.visit_current(path.with_new_choice(0), tree=tree.children[0], debug=debug)
254
+ elif tree.data == 'alias':
255
+ paths |= self.visit_current(path, tree=tree.children[0], debug=debug)
256
+
257
+ return paths
258
+
259
+ def visit_next(self, path: GPath, debug=False):
260
+ paths: set[GPath] = set()
261
+
262
+ if not path.nodes:
263
+ return paths
264
+
265
+ path = self.strip_terminal_nodes(path)
266
+ node = path.nodes[-1]
267
+
268
+ me: Tree
269
+ parent: Tree
270
+ me, parent = self.tree_by_choices(path)
271
+
272
+ check_parent = True
273
+ if isinstance(me, Tree) and me.data == 'expr' and me.children[1] in ['*', '+']:
274
+ if debug: print(' add expr repeat ', path)
275
+ paths |= self.visit_current(path.with_new_choice(0), debug=debug)
276
+ elif isinstance(me, Tree) and me.data == 'maybe':
277
+ if debug: print(' add maybe repeat ', path)
278
+ paths |= self.visit_current(path.with_new_choice(0), debug=debug)
279
+
280
+ if isinstance(parent, Tree) and parent.data == 'expansion' and (node.choices[-1] + 1) < len(parent.children):
281
+ np = path.with_next_choice()
282
+ if debug: print(' move to next sibling ', np)
283
+ paths |= self.visit_current(np, debug=debug)
284
+ check_parent = False
285
+
286
+ if check_parent:
287
+ if len(node.choices) > 0:
288
+ p = path.drop_last_choice()
289
+ if debug: print(' move up to parent tree', p)
290
+ paths |= self.visit_next(p, debug=debug)
291
+ else:
292
+ path = path.drop_last()
293
+ if debug: print(' move up to parent node', path)
294
+ paths |= self.visit_next(path, debug=debug)
295
+
296
+ return paths
297
+
298
+ def tree_by_choices(self, path: GPath, debug=False):
299
+ debug = False
300
+
301
+ c = f'{path.nodes[-1]}'
302
+ p = f'{path.nodes[-1].drop_last_choice()}'
303
+ if c not in self.trees:
304
+ for key, value in self.trees_by_choices(path, debug=debug).items():
305
+ if not key:
306
+ self.trees[f'{path.token().name}'] = value
307
+ else:
308
+ self.trees[f'{path.token().name}-{key}'] = value
309
+
310
+ return self.trees[c], self.trees[p]
311
+
312
+ def trees_by_choices(self, path_or_tree: Union[GPath, Tree], choices: list[int] = [], depth: int = 0, debug=False):
313
+ if debug: print(f'trees_by_choices({path_or_tree}, {choices})')
314
+
315
+ if isinstance(path_or_tree, GPath):
316
+ n = self.find_last_rule(path_or_tree)
317
+ tree = self.rules[n.name]
318
+ else:
319
+ tree = path_or_tree
320
+
321
+ trees: dict[str, Tree] = {}
322
+
323
+ if isinstance(tree, Token):
324
+ if debug: log('tree', debug, depth)
325
+ pass
326
+ elif isinstance(tree, Terminal):
327
+ if debug: log('terminal', debug, depth)
328
+
329
+ choices_str = '-'.join([f"{c}" for c in choices])
330
+ trees[choices_str] = tree
331
+
332
+ pass
333
+ elif isinstance(tree, NonTerminal):
334
+ if debug: log('non-terminal', debug, depth)
335
+ pass
336
+ elif isinstance(tree, tuple):
337
+ if debug: log('tuple', debug, depth)
338
+ trees |= self.trees_by_choices(tree[0], choices, depth=depth, debug=debug)
339
+ elif tree.data in ['expr', 'maybe']:
340
+ if debug: log('expr', debug, depth)
341
+ choices_str = '-'.join([f"{c}" for c in choices])
342
+ trees[choices_str] = tree
343
+
344
+ for child in tree.children[:1]:
345
+ trees |= self.trees_by_choices(child, choices + [0], depth=depth+1, debug=debug)
346
+ elif tree.data == 'value':
347
+ if debug: log('value', debug, depth)
348
+
349
+ choices_str = '-'.join([f"{c}" for c in choices])
350
+ trees[choices_str] = tree
351
+
352
+ for child in tree.children:
353
+ trees |= self.trees_by_choices(child, choices, depth=depth+1, debug=debug)
354
+ elif tree.data == 'literal':
355
+ if debug: log('literal', debug, depth)
356
+
357
+ choices_str = '-'.join([f"{c}" for c in choices])
358
+ trees[choices_str] = tree
359
+
360
+ for child in tree.children:
361
+ trees |= self.trees_by_choices(child, choices, depth=depth+1, debug=debug)
362
+ elif tree.data == 'expansions':
363
+ if debug: log('expansions', debug, depth)
364
+
365
+ choices_str = '-'.join([f"{c}" for c in choices])
366
+ trees[choices_str] = tree
367
+ for i, child in enumerate(tree.children):
368
+ trees |= self.trees_by_choices(child, choices + [i], depth=depth+1, debug=debug)
369
+ elif tree.data == 'expansion':
370
+ if debug: log('expansion', debug, depth)
371
+
372
+ choices_str = '-'.join([f"{c}" for c in choices])
373
+ trees[choices_str] = tree
374
+ for i, child in enumerate(tree.children):
375
+ trees |= self.trees_by_choices(child, choices + [i], depth=depth+1, debug=debug)
376
+ elif tree.data == 'alias':
377
+ if debug: log('alias', debug, depth)
378
+ trees |= self.trees_by_choices(tree.children[0], choices, depth=depth, debug=debug)
379
+ else:
380
+ if debug: log('else', debug, depth)
381
+
382
+ return trees
383
+
384
+ def strip_terminal_nodes(self, path: GPath):
385
+ while path.nodes[-1].name not in self.rules:
386
+ path = path.drop_last()
387
+
388
+ return path
389
+
390
+ def non_terminal_node_name_path(self, path: GPath):
391
+ return self.strip_terminal_nodes(path).name_path()
392
+
393
+ def find_last_rule(self, path: GPath):
394
+ for n in reversed(path.nodes):
395
+ if n.name in self.rules:
396
+ return n
397
+
398
+ return None
399
+
400
+ def build_path(p: str):
401
+ nodes = []
402
+ for n in p.split('.'):
403
+ name_n_choices = n.split('-')
404
+ nodes.append(GNode(name_n_choices[0], choices=[int(c) for c in name_n_choices[1:]]))
405
+
406
+ return GPath(nodes, False)
407
+
408
+ def token_type_for_value(self, token):
409
+ for t in reversed(self.parser.terminals):
410
+ td: TerminalDef = t
411
+ td_value = td.pattern.raw
412
+ if td.pattern.type == 'str':
413
+ lower = False
414
+ if td_value.endswith('i'):
415
+ td_value = td_value[:-1]
416
+ if len(td_value) > 1 and td_value.startswith('"') and td_value.endswith('"'):
417
+ td_value = td_value[1:-1].lower()
418
+ lower = True
419
+ elif len(td_value) > 1 and td_value.startswith('"') and td_value.endswith('"'):
420
+ td_value = td_value[1:-1]
421
+ td_value = td_value.encode('utf-8').decode('unicode-escape')
422
+
423
+ t = token
424
+ if lower:
425
+ t = t.lower()
426
+ if t == td_value:
427
+ return td.name
428
+
429
+ for t in reversed(self.parser.terminals):
430
+ td: TerminalDef = t
431
+ td_value = td.pattern.raw
432
+ if td.pattern.type != 'str' and re.fullmatch(td.pattern.to_regexp(), token):
433
+ return td.name
434
+
435
+ raise Exception('cannot resolve string to a token')
436
+
437
+ def token_type_from_raw(self, raw_token):
438
+ td: TerminalDef = None
439
+ for t in reversed(self.parser.terminals):
440
+ if t.pattern.raw == raw_token:
441
+ td = t
442
+ return td.name
443
+
444
+ return self.token_type_for_value(raw_token.strip('"'))
445
+
446
+ def token_value_from_type(self, token_type: str):
447
+ value = None
448
+
449
+ # print('token_value', token_name)
450
+
451
+ if not self._token_values_by_type:
452
+ for t in self.parser.terminals:
453
+ td: TerminalDef = t
454
+ if td.pattern.type == 'str':
455
+ value = td.pattern.raw
456
+ if value.endswith('i'):
457
+ value = value[:-1]
458
+ if len(value) > 1 and value.startswith('"') and value.endswith('"'):
459
+ value = value[1:-1].lower()
460
+ elif len(value) > 1 and value.startswith('"') and value.endswith('"'):
461
+ value = value[1:-1]
462
+
463
+ value = value.encode('utf-8').decode('unicode-escape')
464
+ elif td.pattern.type == 're' and td.pattern.value == '[0-9]':
465
+ value = '`DIGIT'
466
+ else:
467
+ value = f'`{td.name}'
468
+
469
+ self._token_values_by_type[td.name] = value
470
+
471
+ return self._token_values_by_type[token_type]
472
+
473
+ def next_terminals(self, s: str, debug=False, print_terminals=False, context: dict[any, any] = {}):
474
+ terminals = set()
475
+
476
+ ps = 'start'
477
+ ps = self.find_next_terminals(ps, debug=debug)
478
+ ps0: set[GPath] = None
479
+ last_id_value = None
480
+
481
+ state: str = ''
482
+
483
+ def emit(ps: set[GPath], token_type: str, token_value: str):
484
+ ps0 = ps
485
+ ps = self.choose(ps, token_type, token_value)
486
+ ps = self.find_next_terminals(ps, debug=debug)
487
+
488
+ return ps0, ps
489
+
490
+ token_type_for_a = self.token_type_for_value('a')
491
+ token_type: str = None
492
+ for token in self.parser.lex(s):
493
+ token_type = token.type
494
+ if token_type not in ['IDENTIFIER']:
495
+ token_type = self.token_type_for_value(token)
496
+
497
+ if state == '':
498
+ if token_type == 'QUOTE':
499
+ state = 'quote'
500
+ elif token_type == 'DBLQUOTE':
501
+ state = 'dblquote'
502
+ elif state == 'quote':
503
+ if token_type == 'QUOTE':
504
+ state = 'quote_in_quote'
505
+ elif state == 'dblquote':
506
+ if token_type == 'DBLQUOTE':
507
+ state = 'dblquote_in_dblquote'
508
+ elif state == 'quote_in_quote':
509
+ if token_type == 'QUOTE':
510
+ token_type = token_type_for_a
511
+ state = 'quote'
512
+ else:
513
+ ps0, ps = emit(ps, 'QUOTE', '\'')
514
+ state = ''
515
+ elif state == 'dblquote_in_dblquote':
516
+ if token_type == 'DBLQUOTE':
517
+ token_type = token_type_for_a
518
+ state = 'dblquote'
519
+ else:
520
+ ps0, ps = emit(ps, 'DBLQUOTE', '"')
521
+ state = ''
522
+
523
+ # inside quotes, a word comes back as an IDENTIFIER while escaped quote comes back as their specific anonymous terminals
524
+ if token_type == 'IDENTIFIER' and state in ['quote', 'dblquote']:
525
+ for t1 in token:
526
+ token_type1 = self.token_type_for_value(t1)
527
+
528
+ ps0, ps = emit(ps, token_type1, t1)
529
+ elif state not in ['quote_in_quote', 'dblquote_in_dblquote']:
530
+ ps0, ps = emit(ps, token_type, token)
531
+
532
+ if token_type == 'IDENTIFIER':
533
+ last_id_value = token.value
534
+ context['last-id'] = last_id_value
535
+
536
+ # emit left-overs
537
+ if state == 'quote_in_quote':
538
+ ps0, ps = emit(ps, 'QUOTE', '\'')
539
+ state = ''
540
+ elif state == 'dblquote_in_dblquote':
541
+ ps0, ps = emit(ps, 'DBLQUOTE', '"')
542
+ state = ''
543
+
544
+ if token_type:
545
+ last_value: str = self.token_value_from_type(token_type)
546
+ if not s.endswith(' ') and self.isalpha(last_value):
547
+ ps = ps0
548
+
549
+ for p in ps:
550
+ name_path = self.non_terminal_node_name_path(p)
551
+
552
+ translated = False
553
+ for k, v in self.contexts_by_path.items():
554
+ if k in name_path:
555
+ translated = True
556
+ terminals.add(f'`{v}')
557
+
558
+ if not translated:
559
+ next_value: str = self.token_value_from_type(f'{p.token()}')
560
+
561
+ # TODO hack to pass in all hosts to repl completer
562
+ if not s and next_value == '@':
563
+ terminals.add('`hosts')
564
+ else:
565
+ terminals.add(next_value)
566
+
567
+ if print_terminals:
568
+ print(terminals)
569
+
570
+ return terminals
571
+
572
+ def isalpha(self, value: str):
573
+ return self.contains_letter(value) or value == '*' or self.is_non_terminal_value(value)
574
+
575
+ def is_non_terminal_value(self, value: str):
576
+ return len(value) > 1 and value.startswith('`')
577
+
578
+ def contains_letter(self, s):
579
+ return any(c.isalpha() for c in s)
580
+
581
+ def parse(self, s: str, debug=False, show_terminals=False):
582
+ ps = 'start'
583
+ ps = self.find_next_terminals(ps, debug=debug)
584
+
585
+ for token in self.parser.lex(s):
586
+ token_name = self.token_type_for_value(token)
587
+
588
+ ps = self.choose(ps, token_name)
589
+ ps = self.find_next_terminals(ps, debug=debug)
590
+
591
+ terminals = {p.terminal().strip('"') for p in ps}
592
+
593
+ return terminals
594
+
595
+ def choose(self, paths: list[GPath], token_name: str, token_value: str):
596
+ if LarkParser.show_returns: print(f'\n{token_name}({token_value})\t-----------------------------')
597
+
598
+ ps = set()
599
+ for p in paths:
600
+ # print('choose', token, 'from', p.token())
601
+ if f'{p.token()}' == token_name:
602
+ ps.add(p)
603
+
604
+ return ps