annet 0.0__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.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

Files changed (137) hide show
  1. annet/__init__.py +61 -0
  2. annet/adapters/__init__.py +0 -0
  3. annet/adapters/netbox/__init__.py +0 -0
  4. annet/adapters/netbox/common/__init__.py +0 -0
  5. annet/adapters/netbox/common/client.py +87 -0
  6. annet/adapters/netbox/common/manufacturer.py +62 -0
  7. annet/adapters/netbox/common/models.py +105 -0
  8. annet/adapters/netbox/common/query.py +23 -0
  9. annet/adapters/netbox/common/status_client.py +25 -0
  10. annet/adapters/netbox/common/storage_opts.py +14 -0
  11. annet/adapters/netbox/provider.py +34 -0
  12. annet/adapters/netbox/v24/__init__.py +0 -0
  13. annet/adapters/netbox/v24/api_models.py +73 -0
  14. annet/adapters/netbox/v24/client.py +59 -0
  15. annet/adapters/netbox/v24/storage.py +196 -0
  16. annet/adapters/netbox/v37/__init__.py +0 -0
  17. annet/adapters/netbox/v37/api_models.py +38 -0
  18. annet/adapters/netbox/v37/client.py +62 -0
  19. annet/adapters/netbox/v37/storage.py +149 -0
  20. annet/annet.py +25 -0
  21. annet/annlib/__init__.py +7 -0
  22. annet/annlib/command.py +49 -0
  23. annet/annlib/diff.py +158 -0
  24. annet/annlib/errors.py +8 -0
  25. annet/annlib/filter_acl.py +196 -0
  26. annet/annlib/jsontools.py +116 -0
  27. annet/annlib/lib.py +495 -0
  28. annet/annlib/netdev/__init__.py +0 -0
  29. annet/annlib/netdev/db.py +62 -0
  30. annet/annlib/netdev/devdb/__init__.py +28 -0
  31. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  32. annet/annlib/netdev/views/__init__.py +0 -0
  33. annet/annlib/netdev/views/dump.py +121 -0
  34. annet/annlib/netdev/views/hardware.py +112 -0
  35. annet/annlib/output.py +246 -0
  36. annet/annlib/patching.py +533 -0
  37. annet/annlib/rbparser/__init__.py +0 -0
  38. annet/annlib/rbparser/acl.py +120 -0
  39. annet/annlib/rbparser/deploying.py +55 -0
  40. annet/annlib/rbparser/ordering.py +52 -0
  41. annet/annlib/rbparser/platform.py +51 -0
  42. annet/annlib/rbparser/syntax.py +115 -0
  43. annet/annlib/rulebook/__init__.py +0 -0
  44. annet/annlib/rulebook/common.py +350 -0
  45. annet/annlib/tabparser.py +648 -0
  46. annet/annlib/types.py +35 -0
  47. annet/api/__init__.py +826 -0
  48. annet/argparse.py +415 -0
  49. annet/cli.py +237 -0
  50. annet/cli_args.py +503 -0
  51. annet/configs/context.yml +18 -0
  52. annet/configs/logging.yaml +39 -0
  53. annet/connectors.py +77 -0
  54. annet/deploy.py +536 -0
  55. annet/diff.py +84 -0
  56. annet/executor.py +551 -0
  57. annet/filtering.py +40 -0
  58. annet/gen.py +865 -0
  59. annet/generators/__init__.py +435 -0
  60. annet/generators/base.py +136 -0
  61. annet/generators/common/__init__.py +0 -0
  62. annet/generators/common/initial.py +33 -0
  63. annet/generators/entire.py +97 -0
  64. annet/generators/exceptions.py +10 -0
  65. annet/generators/jsonfragment.py +125 -0
  66. annet/generators/partial.py +119 -0
  67. annet/generators/perf.py +79 -0
  68. annet/generators/ref.py +15 -0
  69. annet/generators/result.py +127 -0
  70. annet/hardware.py +45 -0
  71. annet/implicit.py +139 -0
  72. annet/lib.py +128 -0
  73. annet/output.py +167 -0
  74. annet/parallel.py +448 -0
  75. annet/patching.py +25 -0
  76. annet/reference.py +148 -0
  77. annet/rulebook/__init__.py +114 -0
  78. annet/rulebook/arista/__init__.py +0 -0
  79. annet/rulebook/arista/iface.py +16 -0
  80. annet/rulebook/aruba/__init__.py +16 -0
  81. annet/rulebook/aruba/ap_env.py +146 -0
  82. annet/rulebook/aruba/misc.py +8 -0
  83. annet/rulebook/cisco/__init__.py +0 -0
  84. annet/rulebook/cisco/iface.py +68 -0
  85. annet/rulebook/cisco/misc.py +57 -0
  86. annet/rulebook/cisco/vlandb.py +90 -0
  87. annet/rulebook/common.py +19 -0
  88. annet/rulebook/deploying.py +87 -0
  89. annet/rulebook/huawei/__init__.py +0 -0
  90. annet/rulebook/huawei/aaa.py +75 -0
  91. annet/rulebook/huawei/bgp.py +97 -0
  92. annet/rulebook/huawei/iface.py +33 -0
  93. annet/rulebook/huawei/misc.py +337 -0
  94. annet/rulebook/huawei/vlandb.py +115 -0
  95. annet/rulebook/juniper/__init__.py +107 -0
  96. annet/rulebook/nexus/__init__.py +0 -0
  97. annet/rulebook/nexus/iface.py +92 -0
  98. annet/rulebook/patching.py +143 -0
  99. annet/rulebook/ribbon/__init__.py +12 -0
  100. annet/rulebook/texts/arista.deploy +20 -0
  101. annet/rulebook/texts/arista.order +125 -0
  102. annet/rulebook/texts/arista.rul +59 -0
  103. annet/rulebook/texts/aruba.deploy +20 -0
  104. annet/rulebook/texts/aruba.order +83 -0
  105. annet/rulebook/texts/aruba.rul +87 -0
  106. annet/rulebook/texts/cisco.deploy +27 -0
  107. annet/rulebook/texts/cisco.order +82 -0
  108. annet/rulebook/texts/cisco.rul +105 -0
  109. annet/rulebook/texts/huawei.deploy +188 -0
  110. annet/rulebook/texts/huawei.order +388 -0
  111. annet/rulebook/texts/huawei.rul +471 -0
  112. annet/rulebook/texts/juniper.rul +120 -0
  113. annet/rulebook/texts/nexus.deploy +24 -0
  114. annet/rulebook/texts/nexus.order +85 -0
  115. annet/rulebook/texts/nexus.rul +83 -0
  116. annet/rulebook/texts/nokia.rul +31 -0
  117. annet/rulebook/texts/pc.order +5 -0
  118. annet/rulebook/texts/pc.rul +9 -0
  119. annet/rulebook/texts/ribbon.deploy +22 -0
  120. annet/rulebook/texts/ribbon.rul +77 -0
  121. annet/rulebook/texts/routeros.order +38 -0
  122. annet/rulebook/texts/routeros.rul +45 -0
  123. annet/storage.py +125 -0
  124. annet/tabparser.py +36 -0
  125. annet/text_term_format.py +95 -0
  126. annet/tracing.py +170 -0
  127. annet/types.py +227 -0
  128. annet-0.0.dist-info/AUTHORS +21 -0
  129. annet-0.0.dist-info/LICENSE +21 -0
  130. annet-0.0.dist-info/METADATA +26 -0
  131. annet-0.0.dist-info/RECORD +137 -0
  132. annet-0.0.dist-info/WHEEL +5 -0
  133. annet-0.0.dist-info/entry_points.txt +5 -0
  134. annet-0.0.dist-info/top_level.txt +2 -0
  135. annet_generators/__init__.py +0 -0
  136. annet_generators/example/__init__.py +12 -0
  137. annet_generators/example/lldp.py +53 -0
@@ -0,0 +1,648 @@
1
+ import itertools
2
+ import re
3
+ from collections import OrderedDict as odict
4
+
5
+ from .types import Op
6
+
7
+
8
+ # =====
9
+ class ParserError(Exception):
10
+ pass
11
+
12
+
13
+ # =====
14
+ class _CommentOrEmpty:
15
+ pass
16
+
17
+
18
+ class BlockBegin:
19
+ pass
20
+
21
+
22
+ class BlockEnd:
23
+ pass
24
+
25
+
26
+ # =====
27
+ class CommonFormatter:
28
+ def __init__(self, indent=" "):
29
+ self._indent = indent
30
+ self._block_begin = ""
31
+ self._block_end = ""
32
+ self._statement_end = ""
33
+
34
+ def split(self, text):
35
+ return list(filter(None, text.split("\n")))
36
+
37
+ def join(self, config):
38
+ return "\n".join(
39
+ _filtered_block_marks(
40
+ self._indent_blocks(self._blocks(config, is_patch=False))
41
+ )
42
+ )
43
+
44
+ def diff(self, diff):
45
+ return list(self._diff_lines(diff))
46
+
47
+ def diff_generator(self, diff):
48
+ yield from self._diff_lines(diff)
49
+
50
+ def patch(self, patch):
51
+ return "\n".join(
52
+ _filtered_block_marks(
53
+ self._indent_blocks(self._blocks(patch, is_patch=True))
54
+ )
55
+ )
56
+
57
+ def cmd_paths(self, patch):
58
+ ret = odict()
59
+ path = []
60
+ for row, context in self.blocks_and_context(patch, is_patch=True):
61
+ if row is BlockBegin:
62
+ path.append(path[-1])
63
+ elif row is BlockEnd:
64
+ path.pop()
65
+ else:
66
+ if path:
67
+ path.pop()
68
+ path.append(row)
69
+ ret[tuple(path)] = context
70
+ return ret
71
+
72
+ def patch_plain(self, patch):
73
+ return list(self.cmd_paths(patch).keys())
74
+
75
+ def _diff_lines(self, diff, _level=0, _block_sign=None):
76
+ sign_map = {
77
+ Op.REMOVED: "-",
78
+ Op.ADDED: "+",
79
+ Op.MOVED: ">",
80
+ Op.AFFECTED: " ",
81
+ }
82
+ for (flag, row, children, _) in diff:
83
+ sign = sign_map[flag]
84
+ if not children:
85
+ yield "%s %s%s" % (sign, self._indent * _level, row + self._statement_end)
86
+ else:
87
+ yield "%s %s%s" % (sign, self._indent * _level, row + self._block_begin)
88
+ yield from self._diff_lines(children, _level + 1, sign)
89
+ if _level > 0 and self._block_end and _block_sign is not None:
90
+ yield "%s %s%s" % (_block_sign, self._indent * (_level - 1), self._block_end)
91
+
92
+ def _indented_blocks(self, tree):
93
+ return self._indent_blocks(self._blocks(tree, False))
94
+
95
+ def _indent_blocks(self, blocks):
96
+ _level = 0
97
+ for row in blocks:
98
+ if row is BlockBegin:
99
+ _level += 1
100
+ elif row is BlockEnd:
101
+ _level -= 1
102
+ else:
103
+ row = self._indent * _level + row
104
+ yield row
105
+
106
+ def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
107
+ empty_context = {}
108
+ if is_patch:
109
+ items = ((item.row, item.child, item.context) for item in tree.itms)
110
+ else:
111
+ items = ((row, child, empty_context) for row, child in tree.items())
112
+ for row, sub_config, context in items:
113
+ yield row, context
114
+ if sub_config or (is_patch and sub_config is not None):
115
+ yield BlockBegin, None
116
+ yield from self.blocks_and_context(sub_config, is_patch, _level + 1, row)
117
+ yield BlockEnd, None
118
+
119
+ def _blocks(self, tree, is_patch):
120
+ for row, _context in self.blocks_and_context(tree, is_patch):
121
+ yield row
122
+
123
+
124
+ class BlockExitFormatter(CommonFormatter):
125
+ def __init__(self, block_exit, no_block_exit=(), indent=" "):
126
+ super().__init__(indent)
127
+ self._block_exit = block_exit
128
+ self._no_block_exit = tuple(no_block_exit)
129
+
130
+ def split_remove_spaces(self, text):
131
+ # эта регулярка заменяет 2 и более пробела на один, но оставляет пробелы в начале линии
132
+ text = re.sub(r"(?<=\S)\ {2,}(?=\S)", " ", text)
133
+ res = super().split(text)
134
+ return res
135
+
136
+ def block_exit(self, parent):
137
+ if not (parent and parent.startswith(self._no_block_exit)):
138
+ return self._block_exit
139
+
140
+ def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
141
+ last_context = {}
142
+ for row, context in super().blocks_and_context(tree, is_patch, _level, _parent):
143
+ yield row, context
144
+ if context is not None:
145
+ last_context = context
146
+ if is_patch and _level > 0:
147
+ exit_statement = self.block_exit(_parent)
148
+ if exit_statement:
149
+ yield exit_statement, last_context
150
+
151
+
152
+ class HuaweiFormatter(BlockExitFormatter):
153
+ def __init__(self, indent=" "):
154
+ super().__init__(
155
+ block_exit="quit",
156
+ no_block_exit=[
157
+ "rsa peer-public-key",
158
+ "dsa peer-public-key",
159
+ "public-key-code begin",
160
+ ],
161
+ indent=indent,
162
+ )
163
+
164
+ def split(self, text):
165
+ # на старых прошивка наблюдается баг с двумя пробелами в этом месте в конфиге
166
+ # например на VRP V100R006C00SPC500 + V100R006SPH003
167
+ policy_end_blocks = ("end-list", "endif", "end-filter")
168
+ tree = self.split_remove_spaces(text)
169
+ tree[:] = filter(lambda x: not x.endswith(policy_end_blocks), tree)
170
+ return tree
171
+
172
+ def block_exit(self, parent):
173
+ if parent:
174
+ if parent.startswith("xpl route-filter"):
175
+ return "end-filter"
176
+ elif parent.startswith(("xpl")):
177
+ return "end-list"
178
+ elif parent.startswith("if") and parent.endswith("then"):
179
+ return "endif"
180
+ return super().block_exit(parent)
181
+
182
+
183
+ class CiscoFormatter(BlockExitFormatter):
184
+ def __init__(self, indent=" "):
185
+ super().__init__("exit", indent)
186
+
187
+ def split(self, text):
188
+ return self.split_remove_spaces(text)
189
+
190
+
191
+ class AsrFormatter(BlockExitFormatter):
192
+ def __init__(self, indent=" "):
193
+ super().__init__("exit", indent)
194
+
195
+ def split(self, text):
196
+ policy_end_blocks = ("end-set", "endif", "end-policy")
197
+ tree = self.split_remove_spaces(text)
198
+ tree[:] = filter(lambda x: not x.endswith(policy_end_blocks), tree)
199
+ return tree
200
+
201
+ def block_exit(self, parent):
202
+ if parent:
203
+ if parent.startswith(("prefix-set", "as-path-set", "community-set")):
204
+ return "end-set"
205
+ elif parent.startswith("if") and parent.endswith("then"):
206
+ return "endif"
207
+ elif parent.startswith("route-policy"):
208
+ return "end-policy"
209
+ return super().block_exit(parent)
210
+
211
+
212
+ class JuniperFormatter(CommonFormatter):
213
+ patch_set_prefix = "set "
214
+
215
+ def __init__(self, indent=" "):
216
+ super().__init__(indent)
217
+ self._block_begin = " {"
218
+ self._block_end = "}"
219
+ self._statement_end = ";"
220
+ self._endofline_comment = "; ##"
221
+
222
+ def split(self, text):
223
+ sub_regexs = (
224
+ (re.compile(self._block_begin + r"\s*" + self._block_end + r"$"), ""), # collapse empty blocks
225
+ (re.compile(self._block_begin + "(\t# .+)?$"), ""),
226
+ (re.compile(self._statement_end + r"$"), ""),
227
+ (re.compile(r"\s*" + self._block_end + "(\t# .+)?$"), ""),
228
+ (re.compile(self._endofline_comment + r".*$"), ""),
229
+ )
230
+ split = []
231
+ for line in text.split("\n"):
232
+ for (regex, repl_line) in sub_regexs:
233
+ line = regex.sub(repl_line, line)
234
+ split.append(line)
235
+ return list(filter(None, split))
236
+
237
+ def join(self, config):
238
+ return "\n".join(_filtered_block_marks(self._formatted_blocks(self._indented_blocks(config))))
239
+
240
+ def patch(self, patch):
241
+ return "\n".join(" ".join(x) for x in self.cmd_paths(patch))
242
+
243
+ def patch_plain(self, patch):
244
+ return list(self.cmd_paths(patch).keys())
245
+
246
+ def _formatted_blocks(self, blocks):
247
+ level = 0
248
+ line = None
249
+ for new_line in blocks:
250
+ if new_line is BlockBegin:
251
+ level += 1
252
+ if isinstance(line, str):
253
+ yield line + self._block_begin
254
+ elif new_line is BlockEnd:
255
+ level -= 1
256
+ if isinstance(line, str):
257
+ yield line + self._statement_end
258
+ yield self._indent * level + self._block_end
259
+ elif isinstance(line, str):
260
+ yield line + self._statement_end
261
+ line = new_line
262
+ if isinstance(line, str):
263
+ yield line + self._statement_end
264
+
265
+ def cmd_paths(self, patch, _prev=""):
266
+ commands = odict()
267
+ for item in patch.itms:
268
+ key, childs, context = item.row, item.child, item.context
269
+ if childs:
270
+ for k, v in self.cmd_paths(childs, _prev + " " + key).items():
271
+ commands[k] = v
272
+ else:
273
+ if key.startswith("delete"):
274
+ cmd = "delete" + _prev + " " + key.replace("delete", "", 1).strip()
275
+ elif key.startswith("activate"):
276
+ cmd = "activate" + _prev + " " + key.replace("activate", "", 1).strip()
277
+ elif key.startswith("deactivate"):
278
+ cmd = "deactivate" + _prev + " " + key.replace("deactivate", "", 1).strip()
279
+ else:
280
+ cmd = (self.patch_set_prefix + _prev.strip()).strip() + " " + key
281
+ # Expanding [ a b c ] junipers list of arguments
282
+ matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
283
+ if matches:
284
+ for c in matches.group(2).split(" "):
285
+ if c.strip():
286
+ cmd = " ".join([matches.group(1), c])
287
+ commands[(cmd,)] = context
288
+ else:
289
+ commands[(cmd,)] = context
290
+
291
+ return commands
292
+
293
+
294
+ class RibbonFormatter(JuniperFormatter):
295
+ def __init__(self, *args, **kwargs):
296
+ super().__init__(*args, **kwargs)
297
+ self._endofline_comment = "; # SECRET-DATA"
298
+
299
+
300
+ class JuniperList:
301
+ """
302
+ Форматирует inline-листы в конфиге juniper
303
+ """
304
+
305
+ def __init__(self, *args, spaces=True, **kwargs):
306
+ self._items = list(*args, **kwargs)
307
+ self.spaces = spaces
308
+
309
+ def __str__(self):
310
+ if self.spaces:
311
+ return "[ %s ]" % " ".join(str(_) for _ in self._items)
312
+ else:
313
+ return "[%s]" % " ".join(str(_) for _ in self._items)
314
+
315
+
316
+ class NokiaFormatter(JuniperFormatter):
317
+ patch_set_prefix = "/configure "
318
+
319
+ def __init__(self, *args, **kwargs):
320
+ super().__init__(*args, **kwargs)
321
+ self._statement_end = ""
322
+ self._endofline_comment = " ##"
323
+
324
+ def split(self, text):
325
+ ret = super().split(text)
326
+ # NOCDEVDUTY-248 сдергиваем верхний configure-блок
327
+ # NOCDEVDUTY-282 после configure {} блока могут идти еще блоки которые нам не нужны
328
+ start, finish = None, None
329
+ for i, line in enumerate(ret):
330
+ if line.startswith("#"):
331
+ continue
332
+ # начало configure-блока
333
+ if line == "configure":
334
+ start = i + 1
335
+ # любой после configure последующий блок на глобальном уровне
336
+ elif len(line) == len(line.lstrip()):
337
+ if start is not None and finish is None:
338
+ finish = i
339
+ # Если configure-блока не было - то весь конфиг считаем configre'ом
340
+ start = start if start is not None else 0
341
+ finish = finish if finish is not None else len(ret)
342
+ return ret[start:finish]
343
+
344
+ def cmd_paths(self, patch, _prev=""):
345
+ commands = odict()
346
+ for item in patch.itms:
347
+ key, childs, context = item.row, item.child, item.context
348
+ if childs:
349
+ for k, v in self.cmd_paths(childs, _prev + " " + key).items():
350
+ commands[k] = v
351
+ else:
352
+ if key.startswith("delete"):
353
+ cmd = "/configure delete" + _prev + " " + key.replace("delete", "", 1).strip()
354
+ else:
355
+ cmd = self.patch_set_prefix + _prev.strip() + " " + key
356
+ # Expanding [ a b c ] junipers list of arguments
357
+ matches = re.search(r"^(.*)\s+\[(.+)\]$", cmd)
358
+ if matches:
359
+ for c in matches.group(2).split(" "):
360
+ if c.strip():
361
+ cmd = " ".join([matches.group(1), c])
362
+ commands[(cmd,)] = context
363
+ else:
364
+ commands[(cmd,)] = context
365
+ return commands
366
+
367
+
368
+ class RosFormatter(CommonFormatter):
369
+ patch_set_prefix = "set "
370
+
371
+ def __init__(self, *args, **kwargs):
372
+ super().__init__(*args, **kwargs)
373
+ self._block_begin = "/"
374
+
375
+ def join(self, config):
376
+ return "\n".join(_filtered_block_marks(self._formatted_blocks(self._indented_blocks(config))))
377
+
378
+ def patch(self, patch):
379
+ return "\n".join(" ".join(x) for x in self.cmd_paths(patch))
380
+
381
+ def patch_plain(self, patch):
382
+ return list(self.cmd_paths(patch).keys())
383
+
384
+ def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
385
+ rows = []
386
+ empty_context = {}
387
+ if is_patch:
388
+ items = ((item.row, item.child, item.context) for item in tree.itms)
389
+ else:
390
+ items = ((row, child, empty_context) for row, child in tree.items())
391
+ for row, sub_config, context in items:
392
+ if sub_config or (is_patch and sub_config is not None):
393
+ rows.append((row, sub_config, context))
394
+ else:
395
+ rows.append((row, None, context))
396
+
397
+ prev_prow = None
398
+ for sub_config, row_group in itertools.groupby(rows, lambda x: x[1]):
399
+ if sub_config is None:
400
+ if prev_prow:
401
+ yield prev_prow
402
+ yield BlockBegin, None
403
+ for (row, _, context) in row_group:
404
+ yield row, context
405
+ if prev_prow:
406
+ yield BlockEnd, None
407
+ else:
408
+ for (row, _, context) in row_group:
409
+ if _parent:
410
+ prev_prow = _parent
411
+ prow = f"{_parent} {row}"
412
+ else:
413
+ prow = row
414
+ yield prow, context
415
+ yield BlockBegin, None
416
+ yield from self.blocks_and_context(sub_config, is_patch, _level + 1, prow)
417
+ yield BlockEnd, None
418
+
419
+ def _formatted_blocks(self, blocks):
420
+ line = None
421
+ for new_line in blocks:
422
+ if new_line is BlockBegin:
423
+ if isinstance(line, str):
424
+ yield self._block_begin + line.strip()
425
+ elif isinstance(line, str):
426
+ yield line
427
+ line = new_line
428
+
429
+ def _splitter_file(self, lines):
430
+ filedesrc_re = re.compile(r"^\s+(?P<num>\d+)\s+name=\"(?P<name>[^\"]+)\"\s+type=\"(?P<type>[^\"]+)\""
431
+ r"\s+(size=(?P<size>.*))?creation-time=(?P<time>.*?)(contents=(?P<content>.*)?)?$")
432
+ file_content_indent = re.compile(r"^\s{5}")
433
+ out = []
434
+ files = {}
435
+ curfile = None
436
+ for line in lines:
437
+ match = filedesrc_re.search(line)
438
+ if match:
439
+ if match.group("type").strip() == ".txt file":
440
+ curfile = match.group("name")
441
+ files[curfile] = {"name": curfile, "contents": []}
442
+ if match.group("content"):
443
+ files[curfile]["contents"].append(match.group("content").strip())
444
+ elif curfile and file_content_indent.match(line):
445
+ files[curfile]["contents"].append(file_content_indent.sub("", line))
446
+ for file in files.values():
447
+ out.append(f"print file={file['name']}")
448
+ if len(file["contents"]) > 0:
449
+ text = "\n".join(file["contents"])
450
+ out.append(f"set {file['name']} contents=\"{text}\"")
451
+ return out
452
+
453
+ def _splitter_user_ssh_keys(self, lines):
454
+ keydescr_re = re.compile(r"user=(?P<user>\w+).*key-owner=(?P<owner>.*)$")
455
+ out = []
456
+ for line in lines:
457
+ match = keydescr_re.search(line)
458
+ if match:
459
+ out.append(f"import public-key-file={match.group('owner')}.ssh_key.txt user={match.group('user')}")
460
+
461
+ return out
462
+
463
+ def split(self, text):
464
+ split = []
465
+ level = 0
466
+ postj = {}
467
+ curgroup = None
468
+ for line in text.split("\n"):
469
+ if line.startswith("/"):
470
+ if curgroup:
471
+ for row in getattr(self, curgroup)(postj[curgroup]):
472
+ if level > 0:
473
+ row = row.strip()
474
+ split.append(self._indent * level + row)
475
+
476
+ level = 0
477
+ for group in line.split():
478
+ split.append(self._indent * level + group.replace("/", ""))
479
+ level += 1
480
+
481
+ gpath = line.replace("/", "_splitter_").replace(" ", "_").replace("-", "_")
482
+ if hasattr(self, gpath):
483
+ postj[gpath] = []
484
+ curgroup = gpath
485
+ else:
486
+ curgroup = None
487
+ else:
488
+ row = line
489
+ if curgroup:
490
+ postj[curgroup].append(row)
491
+ else:
492
+ if level > 0:
493
+ row = line.strip()
494
+ split.append(self._indent * level + row)
495
+ if curgroup:
496
+ for row in getattr(self, curgroup)(postj[curgroup]):
497
+ if level > 0:
498
+ row = row.strip()
499
+ split.append(self._indent * level + row)
500
+ return list(filter(None, split))
501
+
502
+ def cmd_paths(self, patch, _prev=""):
503
+ rm_regexs = (
504
+ (re.compile(r"^add "), ""),
505
+ (re.compile(r"^print file="), "name="),
506
+ )
507
+ patch_items = []
508
+ for item in patch.itms:
509
+ key, childs, context = item.row, item.child, item.context
510
+ if childs:
511
+ patch_items.append((key, childs, context))
512
+ else:
513
+ patch_items.append((key, None, context))
514
+
515
+ commands = odict()
516
+ prev_cmd = None
517
+ prev_context = None
518
+ for childs, items in itertools.groupby(patch_items, lambda x: x[1]):
519
+ if childs is None:
520
+ if prev_cmd:
521
+ commands[(prev_cmd,)] = prev_context
522
+ for key, _, context in items:
523
+ if key.startswith("remove"):
524
+ find_cmd = key.replace("remove", "", 1).strip()
525
+ for (regex, repl_line) in rm_regexs:
526
+ find_cmd = regex.sub(repl_line, find_cmd)
527
+ cmd = "remove [ find " + find_cmd + " ]"
528
+ else:
529
+ cmd = key
530
+ commands[(cmd,)] = context
531
+ else:
532
+ for key, _, context in items:
533
+ if _prev:
534
+ prev_cmd = _prev
535
+ prev_context = context
536
+ block_cmd = f"{_prev} {key}"
537
+ else:
538
+ block_cmd = f"/{key}"
539
+ commands[(block_cmd,)] = context
540
+ for k, v in self.cmd_paths(childs, block_cmd).items():
541
+ commands[k] = v
542
+ return commands
543
+
544
+
545
+ def make_formatter(vendor, **kwargs):
546
+ formatters = {
547
+ "juniper": JuniperFormatter,
548
+ "cisco": CiscoFormatter,
549
+ "nexus": CiscoFormatter,
550
+ "huawei": HuaweiFormatter,
551
+ "arista": CiscoFormatter,
552
+ "nokia": NokiaFormatter,
553
+ "routeros": RosFormatter,
554
+ "aruba": CiscoFormatter,
555
+ "pc": CommonFormatter,
556
+ "ribbon": RibbonFormatter,
557
+ }
558
+ return formatters[vendor](**kwargs)
559
+
560
+
561
+ # ====
562
+ def parse_to_tree(text, splitter, comments=("!", "#")):
563
+ tree = odict()
564
+ for stack in _stacked(splitter(text), tuple(comments)):
565
+ local_tree = tree
566
+ for key in stack:
567
+ if key not in local_tree:
568
+ local_tree[key] = odict()
569
+ local_tree = local_tree[key]
570
+ return tree
571
+
572
+
573
+ # =====
574
+ def _stacked(lines, comments):
575
+ stack = []
576
+ for (level, line) in _stripped_indents(lines, comments):
577
+ level += 1
578
+ if level > len(stack):
579
+ stack.append(line)
580
+ elif level == len(stack):
581
+ stack[-1] = line
582
+ else:
583
+ stack = stack[:level - 1] + [line]
584
+ yield tuple(stack)
585
+
586
+
587
+ def _stripped_indents(lines, comments):
588
+ indents = []
589
+ curr_level = 0
590
+ g_level = None
591
+
592
+ for (number, (level, line)) in enumerate(_parsed_indents(lines, comments), start=1):
593
+ if isinstance(line, str):
594
+ if g_level is None:
595
+ g_level = level
596
+ level = level - (g_level or 0)
597
+ if level < 0:
598
+ raise ParserError("Invalid top indention: line %d: %s" % (number, line))
599
+
600
+ if level > curr_level:
601
+ indents.append(level - curr_level)
602
+ curr_level += level - curr_level
603
+ elif level < curr_level:
604
+ while curr_level > level and len(indents):
605
+ curr_level -= indents.pop()
606
+ if curr_level != level:
607
+ raise ParserError("Invalid top indention: line %d: %s" % (number, line))
608
+
609
+ yield (len(indents), line)
610
+
611
+ elif line is BlockEnd:
612
+ indents = []
613
+ curr_level = 0
614
+ g_level = None
615
+
616
+
617
+ def _parsed_indents(lines, comments):
618
+ for line in _filtered_lines(lines, comments):
619
+ if isinstance(line, str):
620
+ yield (_parse_indent(line), line.strip())
621
+ else:
622
+ yield (0, line)
623
+
624
+
625
+ def _filtered_lines(lines, comments):
626
+ for line in lines:
627
+ stripped = line.strip()
628
+ # TODO Это для хуавей, так что хелпер нужно унести в Formatter
629
+ if "#" in comments and line.startswith("#"):
630
+ yield BlockEnd
631
+ elif len(stripped) == 0 or stripped.startswith(comments):
632
+ yield _CommentOrEmpty
633
+ else:
634
+ yield line
635
+
636
+
637
+ def _filtered_block_marks(blocks):
638
+ return filter(lambda b: isinstance(b, str), blocks)
639
+
640
+
641
+ def _parse_indent(line):
642
+ level = 0
643
+ for ch in line:
644
+ if ch in ("\t", " "):
645
+ level += 1
646
+ else:
647
+ break
648
+ return level
annet/annlib/types.py ADDED
@@ -0,0 +1,35 @@
1
+ import enum
2
+ from typing import List, Tuple
3
+
4
+ Diff = List[Tuple]
5
+
6
+
7
+ # Операции отмечающие роль команды в дифе
8
+ # XXX надо отдельно переделать на enum
9
+ class Op:
10
+ ADDED = "added"
11
+ REMOVED = "removed"
12
+ AFFECTED = "affected"
13
+ MOVED = "moved"
14
+ UNCHANGED = "unchanged"
15
+
16
+
17
+ class GeneratorType(enum.Enum):
18
+ PARTIAL = "partial"
19
+ ENTIRE = "entire"
20
+ JSON_FRAGMENT = "json_fragment"
21
+
22
+ @staticmethod
23
+ def fromstring(value: str) -> "GeneratorType":
24
+ return GeneratorType(value)
25
+
26
+ def tostring(self) -> str:
27
+ return self.value
28
+
29
+ def __lt__(self, other: "GeneratorType") -> bool:
30
+ return self.value < other.value
31
+
32
+ def __le__(self, other: "GeneratorType") -> bool:
33
+ if self != other:
34
+ return self.value < other.value
35
+ return True