scrapli 2.0.0a2__py3-none-manylinux2010_x86_64.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.
scrapli/cli.py ADDED
@@ -0,0 +1,1414 @@
1
+ """scrapli.cli"""
2
+
3
+ import importlib.resources
4
+ from collections.abc import Callable
5
+ from ctypes import (
6
+ c_bool,
7
+ c_char_p,
8
+ c_int,
9
+ c_uint,
10
+ c_uint64,
11
+ )
12
+ from enum import Enum
13
+ from logging import getLogger
14
+ from os import environ
15
+ from pathlib import Path
16
+ from types import TracebackType
17
+ from typing import Any
18
+
19
+ from scrapli.auth import Options as AuthOptions
20
+ from scrapli.cli_decorators import handle_operation_timeout, handle_operation_timeout_async
21
+ from scrapli.cli_result import Result
22
+ from scrapli.exceptions import (
23
+ AllocationException,
24
+ CloseException,
25
+ GetResultException,
26
+ NotOpenedException,
27
+ OpenException,
28
+ OperationException,
29
+ OptionsException,
30
+ SubmitOperationException,
31
+ )
32
+ from scrapli.ffi_mapping import LibScrapliMapping
33
+ from scrapli.ffi_types import (
34
+ CancelPointer,
35
+ DriverPointer,
36
+ IntPointer,
37
+ LogFuncCallback,
38
+ OperationIdPointer,
39
+ U64Pointer,
40
+ ZigSlice,
41
+ ZigU64Slice,
42
+ to_c_string,
43
+ )
44
+ from scrapli.helper import (
45
+ resolve_file,
46
+ wait_for_available_operation_result,
47
+ wait_for_available_operation_result_async,
48
+ )
49
+ from scrapli.session import Options as SessionOptions
50
+ from scrapli.transport import Options as TransportOptions
51
+
52
+ CLI_DEFINITIONS_PATH_OVERRIDE_ENV = "SCRAPLI_DEFINITIONS_PATH"
53
+ CLI_DEFINITIONS_PATH_OVERRIDE = environ.get(CLI_DEFINITIONS_PATH_OVERRIDE_ENV)
54
+
55
+
56
+ class InputHandling(str, Enum):
57
+ """
58
+ Enum representing the input handling flavor.
59
+
60
+ Args:
61
+ N/A
62
+
63
+ Returns:
64
+ None
65
+
66
+ Raises:
67
+ N/A
68
+
69
+ """
70
+
71
+ EXACT = "exact"
72
+ FUZZY = "fuzzy"
73
+ IGNORE = "ignore"
74
+
75
+
76
+ class Cli:
77
+ """
78
+ Cli represents a cli connection object.
79
+
80
+ Args:
81
+ N/A
82
+
83
+ Returns:
84
+ None
85
+
86
+ Raises:
87
+ N/A
88
+
89
+ """
90
+
91
+ def __init__( # noqa: PLR0913
92
+ self,
93
+ definition_file_or_name: str,
94
+ host: str,
95
+ *,
96
+ logger_callback: Callable[[int, str], None] | None = None,
97
+ port: int = 22,
98
+ auth_options: AuthOptions | None = None,
99
+ session_options: SessionOptions | None = None,
100
+ transport_options: TransportOptions | None = None,
101
+ logging_uid: str | None = None,
102
+ ) -> None:
103
+ logger_name = f"{__name__}.{host}:{port}"
104
+ if logging_uid is not None:
105
+ logger_name += f":{logging_uid}"
106
+
107
+ self.logger = getLogger(logger_name)
108
+ self._logging_uid = logging_uid
109
+
110
+ self.ffi_mapping = LibScrapliMapping()
111
+
112
+ self.definition_file_or_name = definition_file_or_name
113
+ self._load_definition(definition_file_or_name=definition_file_or_name)
114
+
115
+ # note: many places have encodings done prior to function calls such that the encoded
116
+ # result is not gc'd prior to being used in zig-land, so it looks a bit weird, but thats
117
+ # why. in this case we also just store the host since thats cheap and we need it as a string
118
+ # in places too
119
+ self.host = host
120
+ self._host = to_c_string(host)
121
+
122
+ self.logger_callback = (
123
+ LogFuncCallback(logger_callback) if logger_callback else LogFuncCallback(0)
124
+ )
125
+ self._logger_callback = logger_callback
126
+
127
+ self.port = port
128
+
129
+ self.auth_options = auth_options or AuthOptions()
130
+ self.session_options = session_options or SessionOptions()
131
+ self.transport_options = transport_options or TransportOptions()
132
+
133
+ self.ptr: DriverPointer | None = None
134
+ self.poll_fd: int = 0
135
+
136
+ self._ntc_templates_platform: str | None = None
137
+ self._genie_platform: str | None = None
138
+
139
+ def __enter__(self: "Cli") -> "Cli":
140
+ """
141
+ Enter method for context manager.
142
+
143
+ Args:
144
+ N/A
145
+
146
+ Returns:
147
+ Cli: a concrete implementation of the opened Cli object
148
+
149
+ Raises:
150
+ N/A
151
+
152
+ """
153
+ self.open()
154
+
155
+ return self
156
+
157
+ def __exit__(
158
+ self,
159
+ exception_type: BaseException | None,
160
+ exception_value: BaseException | None,
161
+ traceback: TracebackType | None,
162
+ ) -> None:
163
+ """
164
+ Exit method to cleanup for context manager.
165
+
166
+ Args:
167
+ exception_type: exception type being raised
168
+ exception_value: message from exception being raised
169
+ traceback: traceback from exception being raised
170
+
171
+ Returns:
172
+ None
173
+
174
+ Raises:
175
+ N/A
176
+
177
+ """
178
+ self.close()
179
+
180
+ async def __aenter__(self: "Cli") -> "Cli":
181
+ """
182
+ Enter method for context manager.
183
+
184
+ Args:
185
+ N/A
186
+
187
+ Returns:
188
+ Cli: a concrete implementation of the opened Cli object
189
+
190
+ Raises:
191
+ N/A
192
+
193
+ """
194
+ await self.open_async()
195
+
196
+ return self
197
+
198
+ async def __aexit__(
199
+ self,
200
+ exception_type: BaseException | None,
201
+ exception_value: BaseException | None,
202
+ traceback: TracebackType | None,
203
+ ) -> None:
204
+ """
205
+ Exit method to cleanup for context manager.
206
+
207
+ Args:
208
+ exception_type: exception type being raised
209
+ exception_value: message from exception being raised
210
+ traceback: traceback from exception being raised
211
+
212
+ Returns:
213
+ None
214
+
215
+ Raises:
216
+ N/A
217
+
218
+ """
219
+ await self.close_async()
220
+
221
+ def __str__(self) -> str:
222
+ """
223
+ Magic str method for Cli object.
224
+
225
+ Args:
226
+ N/A
227
+
228
+ Returns:
229
+ str: str representation of Cli
230
+
231
+ Raises:
232
+ N/A
233
+
234
+ """
235
+ return f"scrapli.Cli {self.host}:{self.port}"
236
+
237
+ def __repr__(self) -> str:
238
+ """
239
+ Magic repr method for Cli object.
240
+
241
+ Args:
242
+ N/A
243
+
244
+ Returns:
245
+ str: repr for Cli object
246
+
247
+ Raises:
248
+ N/A
249
+
250
+ """
251
+ return (
252
+ f"{self.__class__.__name__}("
253
+ f"definition_file_or_name={self.definition_file_or_name!r}, "
254
+ f"host={self.host!r}, "
255
+ f"port={self.port!r}, "
256
+ f"auth_options={self.auth_options!r} "
257
+ f"session_options={self.auth_options!r} "
258
+ f"transport_options={self.transport_options!r}) "
259
+ )
260
+
261
+ def __copy__(self, memodict: dict[Any, Any] = {}) -> "Cli":
262
+ # reasonably safely copy of the object... *reasonably*... basically assumes that options
263
+ # will never be mutated during an objects lifetime, which *should* be the case. probably.
264
+ return Cli(
265
+ host=self.host,
266
+ definition_file_or_name=self.definition_file_or_name,
267
+ logger_callback=self._logger_callback,
268
+ port=self.port,
269
+ auth_options=self.auth_options,
270
+ session_options=self.session_options,
271
+ transport_options=self.transport_options,
272
+ logging_uid=self._logging_uid,
273
+ )
274
+
275
+ def _load_definition(self, definition_file_or_name: str) -> None:
276
+ if CLI_DEFINITIONS_PATH_OVERRIDE is not None:
277
+ definition_path = f"{CLI_DEFINITIONS_PATH_OVERRIDE}/{definition_file_or_name}.yaml"
278
+ else:
279
+ definitions_path = importlib.resources.files("scrapli.definitions")
280
+ definition_path = f"{definitions_path}/{definition_file_or_name}.yaml"
281
+
282
+ if Path(definition_path).exists():
283
+ with open(definition_path, "rb") as f:
284
+ self.definition_string = f.read()
285
+
286
+ return
287
+
288
+ if Path(definition_file_or_name).exists():
289
+ with open(definition_file_or_name, "rb") as f:
290
+ self.definition_string = f.read()
291
+
292
+ raise OptionsException(
293
+ f"definition platform name or filename '{definition_file_or_name}' not found"
294
+ )
295
+
296
+ def _ptr_or_exception(self) -> DriverPointer:
297
+ if self.ptr is None:
298
+ raise NotOpenedException
299
+
300
+ return self.ptr
301
+
302
+ def _alloc(
303
+ self,
304
+ ) -> None:
305
+ ptr = self.ffi_mapping.cli_mapping.alloc(
306
+ definition_string=c_char_p(self.definition_string),
307
+ logger_callback=self.logger_callback,
308
+ host=self._host,
309
+ port=c_int(self.port),
310
+ transport_kind=c_char_p(self.transport_options.get_transport_kind()),
311
+ )
312
+ if ptr == 0: # type: ignore[comparison-overlap]
313
+ raise AllocationException("failed to allocate cli")
314
+
315
+ self.ptr = ptr
316
+
317
+ poll_fd = int(
318
+ self.ffi_mapping.shared_mapping.get_poll_fd(
319
+ ptr=self._ptr_or_exception(),
320
+ )
321
+ )
322
+ if poll_fd == 0:
323
+ raise AllocationException("failed to allocate cli")
324
+
325
+ self.poll_fd = poll_fd
326
+
327
+ def _free(
328
+ self,
329
+ ) -> None:
330
+ self.ffi_mapping.shared_mapping.free(ptr=self._ptr_or_exception())
331
+
332
+ @property
333
+ def ntc_templates_platform(self) -> str:
334
+ """
335
+ Returns the ntc templates platform for the cli object.
336
+
337
+ Args:
338
+ N/A
339
+
340
+ Returns:
341
+ str: the ntc templates platform name
342
+
343
+ Raises:
344
+ N/A
345
+
346
+ """
347
+ if self._ntc_templates_platform is not None:
348
+ return self._ntc_templates_platform
349
+
350
+ ntc_templates_platform = ZigSlice(size=c_int(256))
351
+
352
+ status = self.ffi_mapping.cli_mapping.get_ntc_templates_platform(
353
+ ptr=self._ptr_or_exception(),
354
+ ntc_templates_platform=ntc_templates_platform,
355
+ )
356
+ if status != 0:
357
+ raise GetResultException("failed to retrieve ntc templates platform")
358
+
359
+ self._ntc_templates_platform = ntc_templates_platform.get_decoded_contents().strip("\x00")
360
+
361
+ return self._ntc_templates_platform
362
+
363
+ @property
364
+ def genie_platform(self) -> str:
365
+ """
366
+ Returns the genie platform for the cli object.
367
+
368
+ Args:
369
+ N/A
370
+
371
+ Returns:
372
+ str: the genie platform name
373
+
374
+ Raises:
375
+ N/A
376
+
377
+ """
378
+ if self._genie_platform is not None:
379
+ return self._genie_platform
380
+
381
+ genie_platform = ZigSlice(size=c_int(256))
382
+
383
+ status = self.ffi_mapping.cli_mapping.get_ntc_templates_platform(
384
+ ptr=self._ptr_or_exception(),
385
+ ntc_templates_platform=genie_platform,
386
+ )
387
+ if status != 0:
388
+ raise GetResultException("failed to retrieve genie templates platform")
389
+
390
+ self._genie_platform = genie_platform.get_decoded_contents().strip("\x00")
391
+
392
+ return self._genie_platform
393
+
394
+ def _open(
395
+ self,
396
+ operation_id: OperationIdPointer,
397
+ cancel: CancelPointer,
398
+ ) -> c_uint:
399
+ self._alloc()
400
+
401
+ self.auth_options.apply(self.ffi_mapping, self._ptr_or_exception())
402
+ self.session_options.apply(self.ffi_mapping, self._ptr_or_exception())
403
+ self.transport_options.apply(self.ffi_mapping, self._ptr_or_exception())
404
+
405
+ status = self.ffi_mapping.cli_mapping.open(
406
+ ptr=self._ptr_or_exception(),
407
+ operation_id=operation_id,
408
+ cancel=cancel,
409
+ )
410
+ if status != 0:
411
+ self._free()
412
+
413
+ raise OpenException("failed to submit open operation")
414
+
415
+ return c_uint(operation_id.contents.value)
416
+
417
+ def open(
418
+ self,
419
+ ) -> Result:
420
+ """
421
+ Open the cli connection.
422
+
423
+ Args:
424
+ N/A
425
+
426
+ Returns:
427
+ None
428
+
429
+ Raises:
430
+ OpenException: if the operation fails
431
+
432
+ """
433
+ operation_id = OperationIdPointer(c_uint(0))
434
+ cancel = CancelPointer(c_bool(False))
435
+
436
+ operation_id = self._open(operation_id=operation_id, cancel=cancel)
437
+
438
+ return self._get_result(operation_id=operation_id)
439
+
440
+ async def open_async(self) -> Result:
441
+ """
442
+ Open the cli connection.
443
+
444
+ Args:
445
+ N/A
446
+
447
+ Returns:
448
+ None
449
+
450
+ Raises:
451
+ OpenException: if the operation fails
452
+
453
+ """
454
+ operation_id = OperationIdPointer(c_uint(0))
455
+ cancel = CancelPointer(c_bool(False))
456
+
457
+ operation_id = self._open(operation_id=operation_id, cancel=cancel)
458
+
459
+ return await self._get_result_async(operation_id=operation_id)
460
+
461
+ def _close(self) -> c_uint:
462
+ operation_id = OperationIdPointer(c_uint(0))
463
+ cancel = CancelPointer(c_bool(False))
464
+
465
+ status = self.ffi_mapping.cli_mapping.close(
466
+ ptr=self._ptr_or_exception(), operation_id=operation_id, cancel=cancel
467
+ )
468
+ if status != 0:
469
+ raise CloseException("submitting close operation")
470
+
471
+ return c_uint(operation_id.contents.value)
472
+
473
+ def close(
474
+ self,
475
+ ) -> Result:
476
+ """
477
+ Close the cli connection.
478
+
479
+ Args:
480
+ N/A
481
+
482
+ Returns:
483
+ None
484
+
485
+ Raises:
486
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
487
+ CloseException: if the operation fails
488
+
489
+ """
490
+ operation_id = self._close()
491
+
492
+ result = self._get_result(operation_id=operation_id)
493
+
494
+ self._free()
495
+
496
+ return result
497
+
498
+ async def close_async(
499
+ self,
500
+ ) -> Result:
501
+ """
502
+ Close the cli connection.
503
+
504
+ Args:
505
+ N/A
506
+
507
+ Returns:
508
+ None
509
+
510
+ Raises:
511
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
512
+ CloseException: if the operation fails
513
+
514
+ """
515
+ operation_id = self._close()
516
+
517
+ result = await self._get_result_async(operation_id=operation_id)
518
+
519
+ self._free()
520
+
521
+ return result
522
+
523
+ def _get_result(
524
+ self,
525
+ operation_id: c_uint,
526
+ ) -> Result:
527
+ wait_for_available_operation_result(self.poll_fd)
528
+
529
+ operation_count = IntPointer(c_int())
530
+ inputs_size = IntPointer(c_int())
531
+ results_raw_size = IntPointer(c_int())
532
+ results_size = IntPointer(c_int())
533
+ results_failed_indicator_size = IntPointer(c_int())
534
+ err_size = IntPointer(c_int())
535
+
536
+ status = self.ffi_mapping.cli_mapping.fetch_sizes(
537
+ ptr=self._ptr_or_exception(),
538
+ operation_id=operation_id,
539
+ operation_count=operation_count,
540
+ inputs_size=inputs_size,
541
+ results_raw_size=results_raw_size,
542
+ results_size=results_size,
543
+ results_failed_indicator_size=results_failed_indicator_size,
544
+ err_size=err_size,
545
+ )
546
+ if status != 0:
547
+ raise GetResultException("wait operation failed")
548
+
549
+ start_time = U64Pointer(c_uint64())
550
+ splits = ZigU64Slice(size=operation_count.contents)
551
+
552
+ inputs_slice = ZigSlice(size=inputs_size.contents)
553
+ results_raw_slice = ZigSlice(size=results_raw_size.contents)
554
+ results_slice = ZigSlice(size=results_size.contents)
555
+
556
+ results_failed_indicator_slice = ZigSlice(size=results_failed_indicator_size.contents)
557
+ err_slice = ZigSlice(size=err_size.contents)
558
+
559
+ status = self.ffi_mapping.cli_mapping.fetch(
560
+ ptr=self._ptr_or_exception(),
561
+ operation_id=operation_id,
562
+ start_time=start_time,
563
+ splits=splits,
564
+ inputs_slice=inputs_slice,
565
+ results_raw_slice=results_raw_slice,
566
+ results_slice=results_slice,
567
+ results_failed_indicator_slice=results_failed_indicator_slice,
568
+ err_slice=err_slice,
569
+ )
570
+ if status != 0:
571
+ raise GetResultException("fetch operation failed")
572
+
573
+ err_contents = err_slice.get_decoded_contents()
574
+ if err_contents:
575
+ raise OperationException(err_contents)
576
+
577
+ return Result(
578
+ inputs=inputs_slice.get_decoded_contents(),
579
+ host=self.host,
580
+ port=self.port,
581
+ start_time=start_time.contents.value,
582
+ splits=splits.get_contents(),
583
+ results_raw=results_raw_slice.get_contents(),
584
+ results=results_slice.get_decoded_contents(),
585
+ results_failed_indicator=results_failed_indicator_slice.get_decoded_contents(),
586
+ textfsm_platform=self.ntc_templates_platform,
587
+ genie_platform=self.genie_platform,
588
+ )
589
+
590
+ async def _get_result_async(
591
+ self,
592
+ operation_id: c_uint,
593
+ ) -> Result:
594
+ await wait_for_available_operation_result_async(fd=self.poll_fd)
595
+
596
+ operation_count = IntPointer(c_int())
597
+ inputs_size = IntPointer(c_int())
598
+ results_raw_size = IntPointer(c_int())
599
+ results_size = IntPointer(c_int())
600
+ results_failed_indicator_size = IntPointer(c_int())
601
+ err_size = IntPointer(c_int())
602
+
603
+ status = self.ffi_mapping.cli_mapping.fetch_sizes(
604
+ ptr=self._ptr_or_exception(),
605
+ operation_id=operation_id,
606
+ operation_count=operation_count,
607
+ inputs_size=inputs_size,
608
+ results_raw_size=results_raw_size,
609
+ results_size=results_size,
610
+ results_failed_indicator_size=results_failed_indicator_size,
611
+ err_size=err_size,
612
+ )
613
+ if status != 0:
614
+ raise GetResultException("fetch operation sizes failed")
615
+
616
+ start_time = U64Pointer(c_uint64())
617
+ splits = ZigU64Slice(size=operation_count.contents)
618
+
619
+ inputs_slice = ZigSlice(size=inputs_size.contents)
620
+ results_raw_slice = ZigSlice(size=results_raw_size.contents)
621
+ results_slice = ZigSlice(size=results_size.contents)
622
+
623
+ results_failed_indicator_slice = ZigSlice(size=results_failed_indicator_size.contents)
624
+ err_slice = ZigSlice(size=err_size.contents)
625
+
626
+ status = self.ffi_mapping.cli_mapping.fetch(
627
+ ptr=self._ptr_or_exception(),
628
+ operation_id=operation_id,
629
+ start_time=start_time,
630
+ splits=splits,
631
+ inputs_slice=inputs_slice,
632
+ results_raw_slice=results_raw_slice,
633
+ results_slice=results_slice,
634
+ results_failed_indicator_slice=results_failed_indicator_slice,
635
+ err_slice=err_slice,
636
+ )
637
+ if status != 0:
638
+ raise GetResultException("fetch operation failed")
639
+
640
+ err_contents = err_slice.get_decoded_contents()
641
+ if err_contents:
642
+ raise OperationException(err_contents)
643
+
644
+ return Result(
645
+ inputs=inputs_slice.get_decoded_contents(),
646
+ host=self.host,
647
+ port=self.port,
648
+ start_time=start_time.contents.value,
649
+ splits=splits.get_contents(),
650
+ results_raw=results_raw_slice.get_contents(),
651
+ results=results_slice.get_decoded_contents(),
652
+ results_failed_indicator=results_failed_indicator_slice.get_decoded_contents(),
653
+ textfsm_platform=self.ntc_templates_platform,
654
+ genie_platform=self.genie_platform,
655
+ )
656
+
657
+ def _enter_mode(
658
+ self,
659
+ *,
660
+ operation_id: OperationIdPointer,
661
+ cancel: CancelPointer,
662
+ requested_mode: c_char_p,
663
+ ) -> c_uint:
664
+ status = self.ffi_mapping.cli_mapping.enter_mode(
665
+ ptr=self._ptr_or_exception(),
666
+ operation_id=operation_id,
667
+ cancel=cancel,
668
+ requested_mode=requested_mode,
669
+ )
670
+ if status != 0:
671
+ raise SubmitOperationException("submitting get prompt operation failed")
672
+
673
+ return c_uint(operation_id.contents.value)
674
+
675
+ @handle_operation_timeout
676
+ def enter_mode(
677
+ self,
678
+ requested_mode: str,
679
+ *,
680
+ operation_timeout_ns: int | None = None,
681
+ ) -> Result:
682
+ """
683
+ Enter the given mode on the cli connection.
684
+
685
+ Args:
686
+ requested_mode: name of the mode to enter
687
+ operation_timeout_ns: operation timeout in ns for this operation
688
+
689
+ Returns:
690
+ Result: a Result object representing the operation
691
+
692
+ Raises:
693
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
694
+ SubmitOperationException: if the operation fails
695
+
696
+ """
697
+ # only used in the decorator
698
+ _ = operation_timeout_ns
699
+
700
+ operation_id = OperationIdPointer(c_uint(0))
701
+ cancel = CancelPointer(c_bool(False))
702
+
703
+ _requested_mode = to_c_string(requested_mode)
704
+
705
+ operation_id = self._enter_mode(
706
+ operation_id=operation_id, cancel=cancel, requested_mode=_requested_mode
707
+ )
708
+
709
+ return self._get_result(operation_id=operation_id)
710
+
711
+ @handle_operation_timeout_async
712
+ async def enter_mode_async(
713
+ self,
714
+ requested_mode: str,
715
+ *,
716
+ operation_timeout_ns: int | None = None,
717
+ ) -> Result:
718
+ """
719
+ Enter the given mode on the cli connection.
720
+
721
+ Args:
722
+ requested_mode: name of the mode to enter
723
+ operation_timeout_ns: operation timeout in ns for this operation
724
+
725
+ Returns:
726
+ Result: a Result object representing the operation
727
+
728
+ Raises:
729
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
730
+ SubmitOperationException: if the operation fails
731
+
732
+ """
733
+ # only used in the decorator
734
+ _ = operation_timeout_ns
735
+
736
+ operation_id = OperationIdPointer(c_uint(0))
737
+ cancel = CancelPointer(c_bool(False))
738
+
739
+ _requested_mode = to_c_string(requested_mode)
740
+
741
+ operation_id = self._enter_mode(
742
+ operation_id=operation_id, cancel=cancel, requested_mode=_requested_mode
743
+ )
744
+
745
+ return await self._get_result_async(operation_id=operation_id)
746
+
747
+ def _get_prompt(
748
+ self,
749
+ *,
750
+ operation_id: OperationIdPointer,
751
+ cancel: CancelPointer,
752
+ ) -> c_uint:
753
+ status = self.ffi_mapping.cli_mapping.get_prompt(
754
+ ptr=self._ptr_or_exception(),
755
+ operation_id=operation_id,
756
+ cancel=cancel,
757
+ )
758
+ if status != 0:
759
+ raise SubmitOperationException("submitting get prompt operation failed")
760
+
761
+ return c_uint(operation_id.contents.value)
762
+
763
+ @handle_operation_timeout
764
+ def get_prompt(
765
+ self,
766
+ *,
767
+ operation_timeout_ns: int | None = None,
768
+ ) -> Result:
769
+ """
770
+ Get the current prompt of the cli connection.
771
+
772
+ Args:
773
+ operation_timeout_ns: operation timeout in ns for this operation
774
+
775
+ Returns:
776
+ Result: a Result object representing the operation
777
+
778
+ Raises:
779
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
780
+ SubmitOperationException: if the operation fails
781
+
782
+ """
783
+ # only used in the decorator
784
+ _ = operation_timeout_ns
785
+
786
+ operation_id = OperationIdPointer(c_uint(0))
787
+ cancel = CancelPointer(c_bool(False))
788
+
789
+ operation_id = self._get_prompt(operation_id=operation_id, cancel=cancel)
790
+
791
+ return self._get_result(operation_id=operation_id)
792
+
793
+ @handle_operation_timeout_async
794
+ async def get_prompt_async(
795
+ self,
796
+ *,
797
+ operation_timeout_ns: int | None = None,
798
+ ) -> Result:
799
+ """
800
+ Get the current prompt of the cli connection.
801
+
802
+ Args:
803
+ operation_timeout_ns: operation timeout in ns for this operation
804
+
805
+ Returns:
806
+ Result: a Result object representing the operation
807
+
808
+ Raises:
809
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
810
+ SubmitOperationException: if the operation fails
811
+
812
+ """
813
+ # only used in the decorator
814
+ _ = operation_timeout_ns
815
+
816
+ operation_id = OperationIdPointer(c_uint(0))
817
+ cancel = CancelPointer(c_bool(False))
818
+
819
+ operation_id = self._get_prompt(operation_id=operation_id, cancel=cancel)
820
+
821
+ return await self._get_result_async(operation_id=operation_id)
822
+
823
+ def _send_input( # noqa: PLR0913
824
+ self,
825
+ *,
826
+ operation_id: OperationIdPointer,
827
+ cancel: CancelPointer,
828
+ input_: c_char_p,
829
+ requested_mode: c_char_p,
830
+ input_handling: c_char_p,
831
+ retain_input: c_bool,
832
+ retain_trailing_prompt: c_bool,
833
+ ) -> c_uint:
834
+ status = self.ffi_mapping.cli_mapping.send_input(
835
+ ptr=self._ptr_or_exception(),
836
+ operation_id=operation_id,
837
+ cancel=cancel,
838
+ input_=input_,
839
+ requested_mode=requested_mode,
840
+ input_handling=input_handling,
841
+ retain_input=retain_input,
842
+ retain_trailing_prompt=retain_trailing_prompt,
843
+ )
844
+ if status != 0:
845
+ raise SubmitOperationException("submitting send input operation failed")
846
+
847
+ return c_uint(operation_id.contents.value)
848
+
849
+ @handle_operation_timeout
850
+ def send_input( # noqa: PLR0913
851
+ self,
852
+ input_: str,
853
+ *,
854
+ requested_mode: str = "",
855
+ input_handling: InputHandling = InputHandling.FUZZY,
856
+ retain_input: bool = False,
857
+ retain_trailing_prompt: bool = False,
858
+ operation_timeout_ns: int | None = None,
859
+ ) -> Result:
860
+ """
861
+ Send an input on the cli connection.
862
+
863
+ Args:
864
+ input_: the input to send
865
+ requested_mode: name of the mode to send the input at
866
+ input_handling: how to handle the input
867
+ retain_input: retain the input in the final "result"
868
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
869
+ operation_timeout_ns: operation timeout in ns for this operation
870
+
871
+ Returns:
872
+ Result: a Result object representing the operation
873
+
874
+ Raises:
875
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
876
+ SubmitOperationException: if the operation fails
877
+
878
+ """
879
+ # only used in the decorator
880
+ _ = operation_timeout_ns
881
+
882
+ operation_id = OperationIdPointer(c_uint(0))
883
+ cancel = CancelPointer(c_bool(False))
884
+
885
+ _input = to_c_string(input_)
886
+ _requested_mode = to_c_string(requested_mode)
887
+ _input_handling = to_c_string(input_handling)
888
+
889
+ operation_id = self._send_input(
890
+ operation_id=operation_id,
891
+ cancel=cancel,
892
+ input_=_input,
893
+ requested_mode=_requested_mode,
894
+ input_handling=_input_handling,
895
+ retain_input=c_bool(retain_input),
896
+ retain_trailing_prompt=c_bool(retain_trailing_prompt),
897
+ )
898
+
899
+ return self._get_result(operation_id=operation_id)
900
+
901
+ @handle_operation_timeout_async
902
+ async def send_input_async( # noqa: PLR0913
903
+ self,
904
+ input_: str,
905
+ *,
906
+ requested_mode: str = "",
907
+ input_handling: InputHandling = InputHandling.FUZZY,
908
+ retain_input: bool = False,
909
+ retain_trailing_prompt: bool = False,
910
+ operation_timeout_ns: int | None = None,
911
+ ) -> Result:
912
+ """
913
+ Send an input on the cli connection.
914
+
915
+ Args:
916
+ input_: the input to send
917
+ requested_mode: name of the mode to send the input at
918
+ input_handling: how to handle the input
919
+ retain_input: retain the input in the final "result"
920
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
921
+ operation_timeout_ns: operation timeout in ns for this operation
922
+
923
+ Returns:
924
+ Result: a Result object representing the operation
925
+
926
+ Raises:
927
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
928
+ SubmitOperationException: if the operation fails
929
+
930
+ """
931
+ # only used in the decorator
932
+ _ = operation_timeout_ns
933
+
934
+ operation_id = OperationIdPointer(c_uint(0))
935
+ cancel = CancelPointer(c_bool(False))
936
+
937
+ _input = to_c_string(input_)
938
+ _requested_mode = to_c_string(requested_mode)
939
+ _input_handling = to_c_string(input_handling)
940
+
941
+ operation_id = self._send_input(
942
+ operation_id=operation_id,
943
+ cancel=cancel,
944
+ input_=_input,
945
+ requested_mode=_requested_mode,
946
+ input_handling=_input_handling,
947
+ retain_input=c_bool(retain_input),
948
+ retain_trailing_prompt=c_bool(retain_trailing_prompt),
949
+ )
950
+
951
+ return await self._get_result_async(operation_id=operation_id)
952
+
953
+ @handle_operation_timeout
954
+ def send_inputs( # noqa: PLR0913
955
+ self,
956
+ inputs: list[str],
957
+ *,
958
+ requested_mode: str = "",
959
+ input_handling: InputHandling = InputHandling.FUZZY,
960
+ retain_input: bool = False,
961
+ retain_trailing_prompt: bool = False,
962
+ stop_on_indicated_failure: bool = True,
963
+ operation_timeout_ns: int | None = None,
964
+ ) -> Result:
965
+ """
966
+ Send inputs (plural!) on the cli connection.
967
+
968
+ Args:
969
+ inputs: the inputs to send
970
+ requested_mode: name of the mode to send the input at`
971
+ input_handling: how to handle the input
972
+ retain_input: retain the input in the final "result"
973
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
974
+ stop_on_indicated_failure: stops sending inputs at first indicated failure
975
+ operation_timeout_ns: operation timeout in ns for this operation
976
+
977
+ Returns:
978
+ Result: a Result object representing the operation
979
+
980
+ Raises:
981
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
982
+ SubmitOperationException: if the operation fails
983
+
984
+ """
985
+ # only used in the decorator; note that the timeout here is for the whole operation,
986
+ # meaning all the "inputs" combined, not individually
987
+ _ = operation_timeout_ns
988
+
989
+ cancel = CancelPointer(c_bool(False))
990
+
991
+ result: Result | None = None
992
+
993
+ for input_ in inputs:
994
+ operation_id = OperationIdPointer(c_uint(0))
995
+
996
+ _input = to_c_string(input_)
997
+ _requested_mode = to_c_string(requested_mode)
998
+ _input_handling = to_c_string(input_handling)
999
+
1000
+ operation_id = self._send_input(
1001
+ operation_id=operation_id,
1002
+ cancel=cancel,
1003
+ input_=_input,
1004
+ requested_mode=_requested_mode,
1005
+ input_handling=_input_handling,
1006
+ retain_input=c_bool(retain_input),
1007
+ retain_trailing_prompt=c_bool(retain_trailing_prompt),
1008
+ )
1009
+
1010
+ _result = self._get_result(operation_id=operation_id)
1011
+
1012
+ if result is None:
1013
+ result = _result
1014
+ else:
1015
+ result.extend(result=_result)
1016
+
1017
+ if result.failed and stop_on_indicated_failure:
1018
+ return result
1019
+
1020
+ return result # type: ignore[return-value]
1021
+
1022
+ @handle_operation_timeout_async
1023
+ async def send_inputs_async( # noqa: PLR0913
1024
+ self,
1025
+ inputs: list[str],
1026
+ *,
1027
+ requested_mode: str = "",
1028
+ input_handling: InputHandling = InputHandling.FUZZY,
1029
+ retain_input: bool = False,
1030
+ retain_trailing_prompt: bool = False,
1031
+ stop_on_indicated_failure: bool = True,
1032
+ operation_timeout_ns: int | None = None,
1033
+ ) -> Result:
1034
+ """
1035
+ Send inputs (plural!) on the cli connection.
1036
+
1037
+ Args:
1038
+ inputs: the inputs to send
1039
+ requested_mode: name of the mode to send the input at`
1040
+ input_handling: how to handle the input
1041
+ retain_input: retain the input in the final "result"
1042
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
1043
+ stop_on_indicated_failure: stops sending inputs at first indicated failure
1044
+ operation_timeout_ns: operation timeout in ns for this operation
1045
+
1046
+ Returns:
1047
+ MultiResult: a MultiResult object representing the operations
1048
+
1049
+ Raises:
1050
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
1051
+ SubmitOperationException: if the operation fails
1052
+
1053
+ """
1054
+ # only used in the decorator; note that the timeout here is for the whole operation,
1055
+ # meaning all the "inputs" combined, not individually
1056
+ _ = operation_timeout_ns
1057
+
1058
+ cancel = CancelPointer(c_bool(False))
1059
+
1060
+ result: Result | None = None
1061
+
1062
+ for input_ in inputs:
1063
+ operation_id = OperationIdPointer(c_uint(0))
1064
+
1065
+ _input = to_c_string(input_)
1066
+ _requested_mode = to_c_string(requested_mode)
1067
+ _input_handling = to_c_string(input_handling)
1068
+
1069
+ operation_id = self._send_input(
1070
+ operation_id=operation_id,
1071
+ cancel=cancel,
1072
+ input_=_input,
1073
+ requested_mode=_requested_mode,
1074
+ input_handling=_input_handling,
1075
+ retain_input=c_bool(retain_input),
1076
+ retain_trailing_prompt=c_bool(retain_trailing_prompt),
1077
+ )
1078
+
1079
+ _result = await self._get_result_async(operation_id=operation_id)
1080
+ if result is None:
1081
+ result = _result
1082
+ else:
1083
+ result.extend(result=_result)
1084
+
1085
+ if result.failed and stop_on_indicated_failure:
1086
+ return result
1087
+
1088
+ return result # type: ignore[return-value]
1089
+
1090
+ def send_inputs_from_file( # noqa: PLR0913
1091
+ self,
1092
+ f: str,
1093
+ *,
1094
+ requested_mode: str = "",
1095
+ input_handling: InputHandling = InputHandling.FUZZY,
1096
+ retain_input: bool = False,
1097
+ retain_trailing_prompt: bool = False,
1098
+ stop_on_indicated_failure: bool = True,
1099
+ operation_timeout_ns: int | None = None,
1100
+ ) -> Result:
1101
+ """
1102
+ Send inputs (plural! from a file, line-by-line) on the cli connection.
1103
+
1104
+ Args:
1105
+ f: the file to load -- each line will be sent, tralining newlines ignored
1106
+ requested_mode: name of the mode to send the input at`
1107
+ input_handling: how to handle the input
1108
+ retain_input: retain the input in the final "result"
1109
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
1110
+ stop_on_indicated_failure: stops sending inputs at first indicated failure
1111
+ operation_timeout_ns: operation timeout in ns for this operation
1112
+
1113
+ Returns:
1114
+ MultiResult: a MultiResult object representing the operations
1115
+
1116
+ Raises:
1117
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
1118
+ SubmitOperationException: if the operation fails
1119
+
1120
+ """
1121
+ with open(resolve_file(f), encoding="utf-8", mode="r") as _f:
1122
+ inputs = _f.read().splitlines()
1123
+
1124
+ return self.send_inputs(
1125
+ inputs=inputs,
1126
+ requested_mode=requested_mode,
1127
+ input_handling=input_handling,
1128
+ retain_input=retain_input,
1129
+ retain_trailing_prompt=retain_trailing_prompt,
1130
+ stop_on_indicated_failure=stop_on_indicated_failure,
1131
+ operation_timeout_ns=operation_timeout_ns,
1132
+ )
1133
+
1134
+ async def send_inputs_from_file_async( # noqa: PLR0913
1135
+ self,
1136
+ f: str,
1137
+ *,
1138
+ requested_mode: str = "",
1139
+ input_handling: InputHandling = InputHandling.FUZZY,
1140
+ retain_input: bool = False,
1141
+ retain_trailing_prompt: bool = False,
1142
+ stop_on_indicated_failure: bool = True,
1143
+ operation_timeout_ns: int | None = None,
1144
+ ) -> Result:
1145
+ """
1146
+ Send inputs (plural! from a file, line-by-line) on the cli connection.
1147
+
1148
+ Args:
1149
+ f: the file to load -- each line will be sent, tralining newlines ignored
1150
+ requested_mode: name of the mode to send the input at`
1151
+ input_handling: how to handle the input
1152
+ retain_input: retain the input in the final "result"
1153
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
1154
+ stop_on_indicated_failure: stops sending inputs at first indicated failure
1155
+ operation_timeout_ns: operation timeout in ns for this operation
1156
+
1157
+ Returns:
1158
+ MultiResult: a MultiResult object representing the operations
1159
+
1160
+ Raises:
1161
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
1162
+ SubmitOperationException: if the operation fails
1163
+
1164
+ """
1165
+ with open(resolve_file(f), encoding="utf-8", mode="r") as _f:
1166
+ inputs = _f.read().splitlines()
1167
+
1168
+ return await self.send_inputs_async(
1169
+ inputs=inputs,
1170
+ requested_mode=requested_mode,
1171
+ input_handling=input_handling,
1172
+ retain_input=retain_input,
1173
+ retain_trailing_prompt=retain_trailing_prompt,
1174
+ stop_on_indicated_failure=stop_on_indicated_failure,
1175
+ operation_timeout_ns=operation_timeout_ns,
1176
+ )
1177
+
1178
+ def _send_prompted_input( # noqa: PLR0913
1179
+ self,
1180
+ *,
1181
+ operation_id: OperationIdPointer,
1182
+ cancel: CancelPointer,
1183
+ input_: c_char_p,
1184
+ prompt: c_char_p,
1185
+ prompt_pattern: c_char_p,
1186
+ response: c_char_p,
1187
+ hidden_response: c_bool,
1188
+ abort_input: c_char_p,
1189
+ requested_mode: c_char_p,
1190
+ input_handling: c_char_p,
1191
+ retain_trailing_prompt: c_bool,
1192
+ ) -> c_uint:
1193
+ status = self.ffi_mapping.cli_mapping.send_prompted_input(
1194
+ ptr=self._ptr_or_exception(),
1195
+ operation_id=operation_id,
1196
+ cancel=cancel,
1197
+ input_=input_,
1198
+ prompt=prompt,
1199
+ prompt_pattern=prompt_pattern,
1200
+ response=response,
1201
+ hidden_response=hidden_response,
1202
+ abort_input=abort_input,
1203
+ requested_mode=requested_mode,
1204
+ input_handling=input_handling,
1205
+ retain_trailing_prompt=retain_trailing_prompt,
1206
+ )
1207
+ if status != 0:
1208
+ raise SubmitOperationException("submitting send prompted input operation failed")
1209
+
1210
+ return c_uint(operation_id.contents.value)
1211
+
1212
+ @handle_operation_timeout
1213
+ def send_prompted_input( # noqa: PLR0913
1214
+ self,
1215
+ input_: str,
1216
+ prompt: str,
1217
+ prompt_pattern: str,
1218
+ response: str,
1219
+ *,
1220
+ requested_mode: str = "",
1221
+ abort_input: str = "",
1222
+ input_handling: InputHandling = InputHandling.FUZZY,
1223
+ hidden_response: bool = False,
1224
+ retain_trailing_prompt: bool = False,
1225
+ operation_timeout_ns: int | None = None,
1226
+ ) -> Result:
1227
+ """
1228
+ Send a prompted input on the cli connection.
1229
+
1230
+ Args:
1231
+ input_: the input to send
1232
+ prompt: the prompt to respond to (must set this or prompt_pattern)
1233
+ prompt_pattern: the prompt pattern to respond to (must set this or prompt)
1234
+ response: the response to send to the prompt
1235
+ abort_input: the input to send to abort the "prompted input" operation if an error
1236
+ is encountered
1237
+ requested_mode: name of the mode to send the input at
1238
+ input_handling: how to handle the input
1239
+ hidden_response: if the response input will be hidden (like for a password prompt)
1240
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
1241
+ operation_timeout_ns: operation timeout in ns for this operation
1242
+
1243
+ Returns:
1244
+ Result: a Result object representing the operation
1245
+
1246
+ Raises:
1247
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
1248
+ SubmitOperationException: if the operation fails
1249
+
1250
+ """
1251
+ # only used in the decorator
1252
+ _ = operation_timeout_ns
1253
+
1254
+ operation_id = OperationIdPointer(c_uint(0))
1255
+ cancel = CancelPointer(c_bool(False))
1256
+
1257
+ _input = to_c_string(input_)
1258
+ _prompt = to_c_string(prompt)
1259
+ _prompt_pattern = to_c_string(prompt_pattern)
1260
+ _response = to_c_string(response)
1261
+ _abort_input = to_c_string(abort_input)
1262
+ _requested_mode = to_c_string(requested_mode)
1263
+ _input_handling = to_c_string(input_handling)
1264
+
1265
+ operation_id = self._send_prompted_input(
1266
+ operation_id=operation_id,
1267
+ cancel=cancel,
1268
+ input_=_input,
1269
+ prompt=_prompt,
1270
+ prompt_pattern=_prompt_pattern,
1271
+ response=_response,
1272
+ abort_input=_abort_input,
1273
+ requested_mode=_requested_mode,
1274
+ input_handling=_input_handling,
1275
+ hidden_response=c_bool(hidden_response),
1276
+ retain_trailing_prompt=c_bool(retain_trailing_prompt),
1277
+ )
1278
+
1279
+ return self._get_result(operation_id=operation_id)
1280
+
1281
+ @handle_operation_timeout_async
1282
+ async def send_prompted_input_async( # noqa: PLR0913
1283
+ self,
1284
+ input_: str,
1285
+ prompt: str,
1286
+ prompt_pattern: str,
1287
+ response: str,
1288
+ *,
1289
+ abort_input: str = "",
1290
+ requested_mode: str = "",
1291
+ input_handling: InputHandling = InputHandling.FUZZY,
1292
+ hidden_response: bool = False,
1293
+ retain_trailing_prompt: bool = False,
1294
+ operation_timeout_ns: int | None = None,
1295
+ ) -> Result:
1296
+ """
1297
+ Send a prompted input on the cli connection.
1298
+
1299
+ Args:
1300
+ input_: the input to send
1301
+ prompt: the prompt to respond to (must set this or prompt_pattern)
1302
+ prompt_pattern: the prompt pattern to respond to (must set this or prompt)
1303
+ response: the response to send to the prompt
1304
+ abort_input: the input to send to abort the "prompted input" operation if an error
1305
+ is encountered
1306
+ requested_mode: name of the mode to send the input at
1307
+ input_handling: how to handle the input
1308
+ hidden_response: if the response input will be hidden (like for a password prompt)
1309
+ retain_trailing_prompt: retain the trailing prompt in the final "result"
1310
+ operation_timeout_ns: operation timeout in ns for this operation
1311
+
1312
+ Returns:
1313
+ Result: a Result object representing the operation
1314
+
1315
+ Raises:
1316
+ NotOpenedException: if the ptr to the cli object is None (via _ptr_or_exception)
1317
+ SubmitOperationException: if the operation fails
1318
+
1319
+ """
1320
+ # only used in the decorator
1321
+ _ = operation_timeout_ns
1322
+
1323
+ operation_id = OperationIdPointer(c_uint(0))
1324
+ cancel = CancelPointer(c_bool(False))
1325
+
1326
+ _input = to_c_string(input_)
1327
+ _prompt = to_c_string(prompt)
1328
+ _prompt_pattern = to_c_string(prompt_pattern)
1329
+ _response = to_c_string(response)
1330
+ _abort_input = to_c_string(abort_input)
1331
+ _requested_mode = to_c_string(requested_mode)
1332
+ _input_handling = to_c_string(input_handling)
1333
+
1334
+ operation_id = self._send_prompted_input(
1335
+ operation_id=operation_id,
1336
+ cancel=cancel,
1337
+ input_=_input,
1338
+ prompt=_prompt,
1339
+ prompt_pattern=_prompt_pattern,
1340
+ response=_response,
1341
+ abort_input=_abort_input,
1342
+ requested_mode=_requested_mode,
1343
+ input_handling=_input_handling,
1344
+ hidden_response=c_bool(hidden_response),
1345
+ retain_trailing_prompt=c_bool(retain_trailing_prompt),
1346
+ )
1347
+
1348
+ return await self._get_result_async(operation_id=operation_id)
1349
+
1350
+ @staticmethod
1351
+ def ___getwide___() -> None: # pragma: no cover
1352
+ """
1353
+ Dumb inside joke easter egg :)
1354
+
1355
+ Args:
1356
+ N/A
1357
+
1358
+ Returns:
1359
+ None
1360
+
1361
+ Raises:
1362
+ N/A
1363
+
1364
+ """
1365
+ wide = r"""
1366
+ KKKXXXXXXXXXXNNNNNNNNNNNNNNNWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
1367
+ 000000000000KKKKKKKKKKXXXXXXXXXXXXXXXXXNNXXK0Okxdoolllloodxk0KXNNWWNWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNN
1368
+ kkkkkkkOOOOOOOOOOO00000000000000000000kdl:,... ..';coxOKKKKKKKKKKKKXKKXXKKKXXXXXKKKK000
1369
+ kkkkkkkOOOOOOOOOOOO000000000000000Od:,. .,cdOKKKKKKKKKKKK0000OOOOOOOOOOOO
1370
+ kkkkkkkkOOOOOOOOOOO0000000000000kc' .:d0KKKKKKKKK0KKOkOOOOOOOOOO0
1371
+ kkkkkkkkOOOOOOOOOOOO00000000000o' ,o0KKKKKKKKKKOkOOOOOOOOO00
1372
+ kkkkkkkkOOOOOOOOOOOOO000000000o. ;kKKKKKKKKKOkOOOOOOOOO00
1373
+ OOOOOOOOOO0000000000000000K0Kk' 'xKKKKKKKKOkOOOOOOOOO00
1374
+ KKKKKKKKKXXXXXXXXXXXXXXNNNNNNd. cXNNNNNNNK0000O00O0000
1375
+ KKKKKKKKKXXXXXXXXXXXXNNNNNNNXl ............... :XWWWWWWWX000000000000
1376
+ KKKKKKKKKXXXXXXXXXXXXXXNNNNNXc ...''',,,,,,;;,,,,,,'''...... .xWWWWWWWWX000000000000
1377
+ KKKKKKKKKKKXXXXXXXXXXXXXNNNNK; ...',,,,;;;;;;;:::::::;;;;;;,,'. .oNWWWWWWWNK000000OOOO00
1378
+ KKKKKKKKKKKKXXXXXXXXXXXXXXXN0, ...'',,,;;;;;;:::::::::::::::;;;;,'. .dNWWWWWWWWNK0000OOOOOOOO
1379
+ 0000KKKKKKKKKKKKKXXXXXXXXXXN0, ..'',,,,;;;;;;:::::::::::::::::;;;;,,.. ;ONNNNNWWWWWNK00OOOOOOOOOO
1380
+ kkkkkkOOOOOOOOOOOOOOOOOOO000k; ..,,,,,,'',,;;::::::::::::::::;;;;;;,'. .lOKKKKXXKXXKK0OOOOOOOOOOOOO
1381
+ xxxkkkkkkkkkkkkkkkkkkOOOOkdll;..',,,,,,,''...';::ccccc:::::::::;;;;;,...o0000000000000OkkOOOkkOOOOOO
1382
+ xxxxxxkkkkkkkkkkkkkkkkkkOd:;;,..,;;;;;;;;;;,'',,;:ccccccccc:::;;;;;;,..cO0000000000000Oxkkkkkkkkkkkk
1383
+ xxxxxxxxkkkkkkkkkkkkkkkkkl:;;,'';;;;;,'''''',,,,,;::ccc::;,,'.'''',;,,lO00000000000000kxkkkkkkkkkkkk
1384
+ xxxxxxxxkkkkkkkkkkkkkkkkko::;'';;;;;;,''....,'',,,,;:c:;,,'''',,;;;;,:x00000000000000Okxkkkkkkkkkkkk
1385
+ xxxxxxxxxxkkkkkkkkkkkkkkkxl;,,;;;;:::;;;,,,,,,,,,,,,:c:;,'....''',;;,;cxO000000000000Okxkkkkkkkkkkkk
1386
+ kkkkOOOOOOOOOOOOOO00000000x:;;;;;:::c::::::;;;;;;;;;:c:;,,,,'',,',;:::lOKKKKKKXXXXXXKKOkkkkkkkkkkkkk
1387
+ 000000000000000KKKKKKKKKKK0dc;,;;:::ccccccc::::;;;;;:cc:;;;;:::::::::lOXXXXXNNNNNNNNXX0Okkkkkkkkkkkk
1388
+ OO00000000000000000KKKKKKKK0d::;;;::ccccccccc:;;;;;;;:c:;::ccccccc::cOXXXXXXXXXNNNNNXX0kkkkkkkkkkkkk
1389
+ OOO00000000000000000000KKKKKOxxc;;;::ccccccc:;;;;;;;:ccc:::cccllcc;:kKXXXXXXXXXXXXXXXKOkkkkkkkkkkkkk
1390
+ OOOOO00000000000000000000KKK0kdl;;;;;:ccccc::;,,,,;;:clc:::cclllcc:oKXXXXXXXXXXXXXXXXKOkkkkkkkkkkkkk
1391
+ OOOOOOO0000000000000000Okxdlc;,,;;::;;::cc::;;,,,,,;:::;;:cccccc::clxkO00KKKKKKKKKXKK0kkkkkkkkkkkxkk
1392
+ kkkkkkkkkkkkkkkkkkkxdoc:,''.....,;:::;;;::;;;;;;;;;;;;;;;:ccc:::;,',;;:clodxkOOOOOOOOkxxxxxxxxxxxxxx
1393
+ ddddddddddddddoolc;,'''..........,;;:;;;::;,,,,,;;;;;::::::c:::;'.',,;;;;;::clodxkkkkxdxxxxxxxxxxxxx
1394
+ dddddddoolc::;,'''....... ..',;;;;;;;;,'........',;::::::;;,,;;;;;;;;:::::ccloddddxxxxxxxxxxxxx
1395
+ dollc:;,,''......... ..'''',,,,;;;;;,'''.....'',::::;,,;;;::::;;,,;;;;;;;;;::cldxxxxxxdxxdxx
1396
+ l;'''.''...... ..'',,''',,,,;;;::;;,,,,,,;;::;;'.....',;;,,''',,,,,,'',,,',:odxddddddddd
1397
+ ............. .'',,,,,''',,,;;;;::::;::::::;;;........'''''''..'.....,,'...';cdddddddddd
1398
+ . ....... .',,,,,;,,'',,,,;;;::::::::::;;cc. .....''...'''.......','......':odxdddddd
1399
+ ... .',,;;;;;;,'',;;,,,;;;::::::::;cxo....................''''.......'';lddddddd
1400
+ .. .,;,;;;;;;,,,',;;;,,,,;;;;;;;;:dKO:..................''''.. .......',cdddddd
1401
+ ,:;;;;;,,,,;,,;::;,,,,,;::::::dK0c..................'''.. ........',codddd
1402
+ .;:;;;;;,,;;;,,;:;;:;,,;:::::clc,... ...........'''.... .... .....':oddd
1403
+ .',;;;;;;;;;,,;:;;;;,;::::::;'...... ......'......... .....'',cood
1404
+ ..,;;;;;;;;;;;:;;;;:::::;'. . .............. ...''',:od
1405
+ ..',;;;;:;;;:::::::,,'. ............... ....''.':o
1406
+ ...',,;;,,;,,'.. ............... .. .....'c
1407
+ __ _ __.... ................ .... ......'
1408
+ ____ ____ / /_ _ __(_)___/ /__ .............. .. ... .......
1409
+ / __ `/ _ \/ __/ | | /| / / / __ / _ \ ................ . ......
1410
+ / /_/ / __/ /_ | |/ |/ / / /_/ / __/ ................. ......
1411
+ \__, /\___/\__/ |__/|__/_/\__,_/\___/ ............... ......
1412
+ /____/ ............... .. ........
1413
+ """
1414
+ print(wide)