annet 2.1.0__tar.gz → 2.2.1__tar.gz

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 (194) hide show
  1. {annet-2.1.0/annet.egg-info → annet-2.2.1}/PKG-INFO +1 -1
  2. {annet-2.1.0 → annet-2.2.1}/annet/annlib/tabparser.py +3 -0
  3. {annet-2.1.0 → annet-2.2.1}/annet/deploy.py +9 -1
  4. {annet-2.1.0 → annet-2.2.1}/annet/deploy_ui.py +160 -125
  5. {annet-2.1.0 → annet-2.2.1}/annet/text_term_format.py +1 -1
  6. {annet-2.1.0 → annet-2.2.1/annet.egg-info}/PKG-INFO +1 -1
  7. {annet-2.1.0 → annet-2.2.1}/AUTHORS +0 -0
  8. {annet-2.1.0 → annet-2.2.1}/LICENSE +0 -0
  9. {annet-2.1.0 → annet-2.2.1}/MANIFEST.in +0 -0
  10. {annet-2.1.0 → annet-2.2.1}/README.md +0 -0
  11. {annet-2.1.0 → annet-2.2.1}/annet/__init__.py +0 -0
  12. {annet-2.1.0 → annet-2.2.1}/annet/adapters/__init__.py +0 -0
  13. {annet-2.1.0 → annet-2.2.1}/annet/adapters/fetchers/__init__.py +0 -0
  14. {annet-2.1.0 → annet-2.2.1}/annet/adapters/fetchers/stub/__init__.py +0 -0
  15. {annet-2.1.0 → annet-2.2.1}/annet/adapters/fetchers/stub/fetcher.py +0 -0
  16. {annet-2.1.0 → annet-2.2.1}/annet/adapters/file/__init__.py +0 -0
  17. {annet-2.1.0 → annet-2.2.1}/annet/adapters/file/provider.py +0 -0
  18. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/__init__.py +0 -0
  19. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/__init__.py +0 -0
  20. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/client.py +0 -0
  21. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/manufacturer.py +0 -0
  22. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/models.py +0 -0
  23. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/query.py +0 -0
  24. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/status_client.py +0 -0
  25. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/common/storage_opts.py +0 -0
  26. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/provider.py +0 -0
  27. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/v24/__init__.py +0 -0
  28. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/v24/storage.py +0 -0
  29. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/v37/__init__.py +0 -0
  30. {annet-2.1.0 → annet-2.2.1}/annet/adapters/netbox/v37/storage.py +0 -0
  31. {annet-2.1.0 → annet-2.2.1}/annet/annet.py +0 -0
  32. {annet-2.1.0 → annet-2.2.1}/annet/annlib/__init__.py +0 -0
  33. {annet-2.1.0 → annet-2.2.1}/annet/annlib/command.py +0 -0
  34. {annet-2.1.0 → annet-2.2.1}/annet/annlib/diff.py +0 -0
  35. {annet-2.1.0 → annet-2.2.1}/annet/annlib/errors.py +0 -0
  36. {annet-2.1.0 → annet-2.2.1}/annet/annlib/filter_acl.py +0 -0
  37. {annet-2.1.0 → annet-2.2.1}/annet/annlib/jsontools.py +0 -0
  38. {annet-2.1.0 → annet-2.2.1}/annet/annlib/lib.py +0 -0
  39. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/__init__.py +0 -0
  40. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/db.py +0 -0
  41. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/devdb/__init__.py +0 -0
  42. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  43. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/views/__init__.py +0 -0
  44. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/views/dump.py +0 -0
  45. {annet-2.1.0 → annet-2.2.1}/annet/annlib/netdev/views/hardware.py +0 -0
  46. {annet-2.1.0 → annet-2.2.1}/annet/annlib/output.py +0 -0
  47. {annet-2.1.0 → annet-2.2.1}/annet/annlib/patching.py +0 -0
  48. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rbparser/__init__.py +0 -0
  49. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rbparser/acl.py +0 -0
  50. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rbparser/deploying.py +0 -0
  51. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rbparser/ordering.py +0 -0
  52. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rbparser/platform.py +0 -0
  53. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rbparser/syntax.py +0 -0
  54. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rulebook/__init__.py +0 -0
  55. {annet-2.1.0 → annet-2.2.1}/annet/annlib/rulebook/common.py +0 -0
  56. {annet-2.1.0 → annet-2.2.1}/annet/annlib/types.py +0 -0
  57. {annet-2.1.0 → annet-2.2.1}/annet/api/__init__.py +0 -0
  58. {annet-2.1.0 → annet-2.2.1}/annet/argparse.py +0 -0
  59. {annet-2.1.0 → annet-2.2.1}/annet/bgp_models.py +0 -0
  60. {annet-2.1.0 → annet-2.2.1}/annet/cli.py +0 -0
  61. {annet-2.1.0 → annet-2.2.1}/annet/cli_args.py +0 -0
  62. {annet-2.1.0 → annet-2.2.1}/annet/configs/context.yml +0 -0
  63. {annet-2.1.0 → annet-2.2.1}/annet/configs/logging.yaml +0 -0
  64. {annet-2.1.0 → annet-2.2.1}/annet/connectors.py +0 -0
  65. {annet-2.1.0 → annet-2.2.1}/annet/diff.py +0 -0
  66. {annet-2.1.0 → annet-2.2.1}/annet/executor.py +0 -0
  67. {annet-2.1.0 → annet-2.2.1}/annet/filtering.py +0 -0
  68. {annet-2.1.0 → annet-2.2.1}/annet/gen.py +0 -0
  69. {annet-2.1.0 → annet-2.2.1}/annet/generators/__init__.py +0 -0
  70. {annet-2.1.0 → annet-2.2.1}/annet/generators/base.py +0 -0
  71. {annet-2.1.0 → annet-2.2.1}/annet/generators/common/__init__.py +0 -0
  72. {annet-2.1.0 → annet-2.2.1}/annet/generators/common/initial.py +0 -0
  73. {annet-2.1.0 → annet-2.2.1}/annet/generators/entire.py +0 -0
  74. {annet-2.1.0 → annet-2.2.1}/annet/generators/exceptions.py +0 -0
  75. {annet-2.1.0 → annet-2.2.1}/annet/generators/jsonfragment.py +0 -0
  76. {annet-2.1.0 → annet-2.2.1}/annet/generators/partial.py +0 -0
  77. {annet-2.1.0 → annet-2.2.1}/annet/generators/perf.py +0 -0
  78. {annet-2.1.0 → annet-2.2.1}/annet/generators/ref.py +0 -0
  79. {annet-2.1.0 → annet-2.2.1}/annet/generators/result.py +0 -0
  80. {annet-2.1.0 → annet-2.2.1}/annet/hardware.py +0 -0
  81. {annet-2.1.0 → annet-2.2.1}/annet/implicit.py +0 -0
  82. {annet-2.1.0 → annet-2.2.1}/annet/lib.py +0 -0
  83. {annet-2.1.0 → annet-2.2.1}/annet/mesh/__init__.py +0 -0
  84. {annet-2.1.0 → annet-2.2.1}/annet/mesh/basemodel.py +0 -0
  85. {annet-2.1.0 → annet-2.2.1}/annet/mesh/device_models.py +0 -0
  86. {annet-2.1.0 → annet-2.2.1}/annet/mesh/executor.py +0 -0
  87. {annet-2.1.0 → annet-2.2.1}/annet/mesh/match_args.py +0 -0
  88. {annet-2.1.0 → annet-2.2.1}/annet/mesh/models_converter.py +0 -0
  89. {annet-2.1.0 → annet-2.2.1}/annet/mesh/peer_models.py +0 -0
  90. {annet-2.1.0 → annet-2.2.1}/annet/mesh/port_processor.py +0 -0
  91. {annet-2.1.0 → annet-2.2.1}/annet/mesh/registry.py +0 -0
  92. {annet-2.1.0 → annet-2.2.1}/annet/output.py +0 -0
  93. {annet-2.1.0 → annet-2.2.1}/annet/parallel.py +0 -0
  94. {annet-2.1.0 → annet-2.2.1}/annet/patching.py +0 -0
  95. {annet-2.1.0 → annet-2.2.1}/annet/reference.py +0 -0
  96. {annet-2.1.0 → annet-2.2.1}/annet/rpl/__init__.py +0 -0
  97. {annet-2.1.0 → annet-2.2.1}/annet/rpl/action.py +0 -0
  98. {annet-2.1.0 → annet-2.2.1}/annet/rpl/condition.py +0 -0
  99. {annet-2.1.0 → annet-2.2.1}/annet/rpl/match_builder.py +0 -0
  100. {annet-2.1.0 → annet-2.2.1}/annet/rpl/policy.py +0 -0
  101. {annet-2.1.0 → annet-2.2.1}/annet/rpl/result.py +0 -0
  102. {annet-2.1.0 → annet-2.2.1}/annet/rpl/routemap.py +0 -0
  103. {annet-2.1.0 → annet-2.2.1}/annet/rpl/statement_builder.py +0 -0
  104. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/__init__.py +0 -0
  105. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/aspath.py +0 -0
  106. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/community.py +0 -0
  107. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/cumulus_frr.py +0 -0
  108. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/entities.py +0 -0
  109. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/execute.py +0 -0
  110. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/policy.py +0 -0
  111. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/prefix_lists.py +0 -0
  112. {annet-2.1.0 → annet-2.2.1}/annet/rpl_generators/rd.py +0 -0
  113. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/__init__.py +0 -0
  114. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/arista/__init__.py +0 -0
  115. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/arista/aaa.py +0 -0
  116. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/arista/iface.py +0 -0
  117. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/aruba/__init__.py +0 -0
  118. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/aruba/ap_env.py +0 -0
  119. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/aruba/misc.py +0 -0
  120. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/b4com/__init__.py +0 -0
  121. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/b4com/file.py +0 -0
  122. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/b4com/iface.py +0 -0
  123. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/cisco/__init__.py +0 -0
  124. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/cisco/iface.py +0 -0
  125. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/cisco/misc.py +0 -0
  126. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/cisco/vlandb.py +0 -0
  127. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/common.py +0 -0
  128. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/deploying.py +0 -0
  129. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/huawei/__init__.py +0 -0
  130. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/huawei/aaa.py +0 -0
  131. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/huawei/bgp.py +0 -0
  132. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/huawei/iface.py +0 -0
  133. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/huawei/misc.py +0 -0
  134. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/huawei/vlandb.py +0 -0
  135. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/juniper/__init__.py +0 -0
  136. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/nexus/__init__.py +0 -0
  137. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/nexus/iface.py +0 -0
  138. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/patching.py +0 -0
  139. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/routeros/__init__.py +0 -0
  140. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/routeros/file.py +0 -0
  141. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/arista.deploy +0 -0
  142. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/arista.order +0 -0
  143. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/arista.rul +0 -0
  144. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/aruba.deploy +0 -0
  145. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/aruba.order +0 -0
  146. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/aruba.rul +0 -0
  147. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/b4com.deploy +0 -0
  148. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/b4com.order +0 -0
  149. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/b4com.rul +0 -0
  150. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/cisco.deploy +0 -0
  151. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/cisco.order +0 -0
  152. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/cisco.rul +0 -0
  153. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/huawei.deploy +0 -0
  154. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/huawei.order +0 -0
  155. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/huawei.rul +0 -0
  156. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/juniper.order +0 -0
  157. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/juniper.rul +0 -0
  158. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/nexus.deploy +0 -0
  159. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/nexus.order +0 -0
  160. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/nexus.rul +0 -0
  161. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/nokia.rul +0 -0
  162. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/optixtrans.deploy +0 -0
  163. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/optixtrans.order +0 -0
  164. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/optixtrans.rul +0 -0
  165. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/pc.deploy +0 -0
  166. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/pc.order +0 -0
  167. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/pc.rul +0 -0
  168. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/ribbon.deploy +0 -0
  169. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/ribbon.rul +0 -0
  170. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/routeros.order +0 -0
  171. {annet-2.1.0 → annet-2.2.1}/annet/rulebook/texts/routeros.rul +0 -0
  172. {annet-2.1.0 → annet-2.2.1}/annet/storage.py +0 -0
  173. {annet-2.1.0 → annet-2.2.1}/annet/tabparser.py +0 -0
  174. {annet-2.1.0 → annet-2.2.1}/annet/tracing.py +0 -0
  175. {annet-2.1.0 → annet-2.2.1}/annet/types.py +0 -0
  176. {annet-2.1.0 → annet-2.2.1}/annet.egg-info/SOURCES.txt +0 -0
  177. {annet-2.1.0 → annet-2.2.1}/annet.egg-info/dependency_links.txt +0 -0
  178. {annet-2.1.0 → annet-2.2.1}/annet.egg-info/entry_points.txt +0 -0
  179. {annet-2.1.0 → annet-2.2.1}/annet.egg-info/requires.txt +0 -0
  180. {annet-2.1.0 → annet-2.2.1}/annet.egg-info/top_level.txt +0 -0
  181. {annet-2.1.0 → annet-2.2.1}/annet_generators/__init__.py +0 -0
  182. {annet-2.1.0 → annet-2.2.1}/annet_generators/example/__init__.py +0 -0
  183. {annet-2.1.0 → annet-2.2.1}/annet_generators/example/lldp.py +0 -0
  184. {annet-2.1.0 → annet-2.2.1}/annet_generators/mesh_example/__init__.py +0 -0
  185. {annet-2.1.0 → annet-2.2.1}/annet_generators/mesh_example/bgp.py +0 -0
  186. {annet-2.1.0 → annet-2.2.1}/annet_generators/mesh_example/mesh_logic.py +0 -0
  187. {annet-2.1.0 → annet-2.2.1}/annet_generators/rpl_example/__init__.py +0 -0
  188. {annet-2.1.0 → annet-2.2.1}/annet_generators/rpl_example/generator.py +0 -0
  189. {annet-2.1.0 → annet-2.2.1}/annet_generators/rpl_example/items.py +0 -0
  190. {annet-2.1.0 → annet-2.2.1}/annet_generators/rpl_example/mesh.py +0 -0
  191. {annet-2.1.0 → annet-2.2.1}/annet_generators/rpl_example/route_policy.py +0 -0
  192. {annet-2.1.0 → annet-2.2.1}/requirements.txt +0 -0
  193. {annet-2.1.0 → annet-2.2.1}/setup.cfg +0 -0
  194. {annet-2.1.0 → annet-2.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 2.1.0
3
+ Version: 2.2.1
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -404,6 +404,9 @@ class JuniperPatch:
404
404
  def __iter__(self):
405
405
  return iter(item[0] for item in self._items)
406
406
 
407
+ def __bool__(self) -> bool:
408
+ return bool(self._items)
409
+
407
410
 
408
411
  class JuniperFormatter(CommonFormatter):
409
412
  patch_set_prefix = "set"
@@ -23,6 +23,14 @@ class ProgressBar(abc.ABC):
23
23
  def set_content(self, tile_name: str, content: str):
24
24
  ...
25
25
 
26
+ @abc.abstractmethod
27
+ def add_content(self, tile_name: str, content: str):
28
+ ...
29
+
30
+ @abc.abstractmethod
31
+ def reset_content(self, tile_name: str):
32
+ ...
33
+
26
34
  @abc.abstractmethod
27
35
  def set_progress(self,
28
36
  tile_name: str,
@@ -36,7 +44,7 @@ class ProgressBar(abc.ABC):
36
44
  ...
37
45
 
38
46
  @abc.abstractmethod
39
- def set_exception(self, tile_name: str, cmd_exc: str, last_cmd: str, progress_max: int):
47
+ def set_exception(self, tile_name: str, cmd_exc: str, last_cmd: str, progress_max: int, content: str = "") -> None:
40
48
  ...
41
49
 
42
50
 
@@ -7,12 +7,16 @@ import math
7
7
  import asyncio
8
8
  import textwrap
9
9
  import time
10
+ import traceback
10
11
  from contextlib import contextmanager
12
+ from dataclasses import dataclass
13
+ from enum import Enum
11
14
  from typing import Dict, List, Optional, Any
12
15
 
13
16
  from contextlog import get_logger
14
17
 
15
18
  from annet import text_term_format
19
+ from annet.deploy import ProgressBar
16
20
  from annet.output import TextArgs
17
21
  try:
18
22
  import curses
@@ -21,6 +25,7 @@ except ImportError:
21
25
 
22
26
  uname = os.uname()[0]
23
27
  NCURSES_SIZE_T = 2 ** 15 - 1
28
+ MIN_CONTENT_HEIGHT = 20
24
29
 
25
30
 
26
31
  class AskConfirm:
@@ -348,20 +353,42 @@ def init_colors():
348
353
  }
349
354
 
350
355
 
351
- class ProgressBars:
352
- TAIL_MODE_UNIFORM = 1 # экран разбивается на несколько равнозначных частей и в них есть заголовок и текст
353
- TAIL_MODE_ONE_CONTENT = 2 # есть только одно окно с контентом, остальные только с заголовками
354
- TAIL_MODE_NO_CONTENT_ONE_COLUMN = 3
356
+ @dataclass
357
+ class Tile:
358
+ win: "curses.window | None"
359
+ content: list[list[TextArgs]]
360
+ title: list[str]
361
+ height: int
362
+ width: int
363
+ need_draw: bool = True
364
+ total: int = 0
365
+ iteration: int = 0
355
366
 
367
+
368
+ class UiState(Enum):
369
+ INIT = "INIT"
370
+ OK = "OK"
371
+ WAIT_INPUT = "WAIT_INPUT"
372
+ CLOSED = "CLOSED"
373
+
374
+
375
+ class TailMode(Enum):
376
+ UNIFORM = "UNIFORM" # display is split into several parts with content and headers
377
+ ONE_CONTENT = "ONE_CONTENT" # only one part displays content, others show headers
378
+ NO_CONTENT = "NO_CONTENT" # only headers
379
+
380
+
381
+ class ProgressBars(ProgressBar):
356
382
  def __init__(self, tiles_params: dict[str, dict[Any, Any]]):
357
383
  self.tiles_params = tiles_params
358
- self.mode = self.TAIL_MODE_UNIFORM
359
- self.screen: "curses.window" = None
360
- self.tiles: dict[str, dict[str, Any]] = {}
384
+ self.mode: TailMode = TailMode.UNIFORM
385
+ self.screen: "curses.window | None" = None
386
+ self.tiles: dict[str, Tile] = {}
361
387
  self.offset = [0, 0]
362
388
  self.terminal_refresher_coro = None
363
389
  self.color_to_curses: dict[Optional[str], int] = {}
364
- self.state = "INIT"
390
+ self.state: UiState = UiState.INIT
391
+ self.active_tile: int = len(tiles_params) - 1 # tiles with content have numbers from 1
365
392
 
366
393
  # context
367
394
  self.enter_ok = False
@@ -397,14 +424,14 @@ class ProgressBars:
397
424
 
398
425
  def evaluate_mode(self, scree_size):
399
426
  height = scree_size[0] // len(self.tiles_params)
400
- if height > 20:
401
- return self.TAIL_MODE_UNIFORM
402
- if scree_size[0] - len(self.tiles_params) > 20:
403
- return self.TAIL_MODE_ONE_CONTENT
427
+ if height > MIN_CONTENT_HEIGHT:
428
+ return TailMode.UNIFORM
429
+ if scree_size[0] - len(self.tiles_params) > MIN_CONTENT_HEIGHT:
430
+ return TailMode.ONE_CONTENT
404
431
  if scree_size[0] // len(self.tiles_params) > 1:
405
- return self.TAIL_MODE_NO_CONTENT_ONE_COLUMN
432
+ return TailMode.NO_CONTENT
406
433
  else:
407
- return self.TAIL_MODE_NO_CONTENT_ONE_COLUMN
434
+ return TailMode.NO_CONTENT
408
435
 
409
436
  def make_tiles(self):
410
437
  import curses
@@ -421,8 +448,10 @@ class ProgressBars:
421
448
  begin_x = 0
422
449
  tile_no = 0
423
450
  status_bar_win = curses.newwin(1, width, scree_size[0], 0)
424
- self.tiles["status:"] = {"win": status_bar_win, "content": "init", "title": [""], "height": 1,
425
- "width": width, "need_draw": True}
451
+ self.tiles["status:"] = Tile(
452
+ win=status_bar_win, content=[], title=[""],
453
+ height=1, width=width, need_draw=True,
454
+ )
426
455
  max_tile_name_len = max(len(tile_name) for tile_name in self.tiles_params)
427
456
 
428
457
  for tile_name in self.tiles_params:
@@ -430,16 +459,16 @@ class ProgressBars:
430
459
  height = 0
431
460
  tile_no += 1
432
461
 
433
- if mode == self.TAIL_MODE_UNIFORM:
462
+ if mode is TailMode.UNIFORM:
434
463
  height = int(scree_size[0] // len(self.tiles_params)) # TODO:остаток от деления прибавить к последнему
435
464
  win = curses.newwin(height, width, begin_y, begin_x)
436
- elif mode == self.TAIL_MODE_ONE_CONTENT:
437
- if i == tiles_count - 1:
465
+ elif mode is TailMode.ONE_CONTENT:
466
+ if i == self.active_tile:
438
467
  height = scree_size[0] - tiles_count + 1
439
468
  else:
440
469
  height = 1
441
470
  win = curses.newwin(height, width, begin_y, begin_x)
442
- elif mode == self.TAIL_MODE_NO_CONTENT_ONE_COLUMN:
471
+ elif mode is TailMode.NO_CONTENT:
443
472
  height = 1
444
473
 
445
474
  if tile_no < max_height:
@@ -449,26 +478,51 @@ class ProgressBars:
449
478
  win = curses.newwin(height, width, begin_y, begin_x)
450
479
  if tile_no == max_height:
451
480
  left = len(self.tiles_params) - max_height + 1
452
- self.tiles["dumb"] = {"win": curses.newwin(height, width, begin_y, begin_x),
453
- "content": "init",
454
- "title": ["... and %s more" % left],
455
- "height": height,
456
- "width": width,
457
- "need_draw": True}
458
-
459
- title = [("{:<%s}" % (max_tile_name_len)).format(tile_name)]
460
-
461
- self.tiles[tile_name] = {
462
- "win": win,
463
- "content": "init",
464
- "title": title,
465
- "height": height,
466
- "width": width,
467
- "need_draw": True
468
- }
481
+ self.tiles["dumb"] = Tile(
482
+ win=curses.newwin(height, width, begin_y, begin_x),
483
+ content=[],
484
+ title=["... and %s more" % left],
485
+ height=height, width=width, need_draw=True,
486
+ )
487
+
488
+ self.tiles[tile_name] = Tile(
489
+ win=win,
490
+ content=[],
491
+ title=[("{:<%s}" % (max_tile_name_len)).format(tile_name)],
492
+ height=height, width=width, need_draw=True,
493
+ )
469
494
  i += 1
470
495
  begin_y += height
471
496
 
497
+ def _next_active_tile(self):
498
+ self._set_active_tile((self.active_tile + 1) % len(self.tiles_params))
499
+
500
+ def _prev_active_tile(self):
501
+ self._set_active_tile((self.active_tile - 1) % len(self.tiles_params))
502
+
503
+ def _set_active_tile(self, active_tile: int) -> None:
504
+ self.active_tile = active_tile
505
+ if self.mode is TailMode.ONE_CONTENT:
506
+ return
507
+
508
+ scree_size = self.screen.getmaxyx()
509
+ scree_offset = self.screen.getbegyx()
510
+ tiles_count = len(self.tiles_params)
511
+ width = scree_size[1]
512
+ begin_y = scree_offset[0]
513
+ for n, dev in enumerate(self.tiles_params):
514
+ tile = self.tiles[dev]
515
+ if n == self.active_tile:
516
+ height = scree_size[0] - tiles_count
517
+ else:
518
+ height = 1
519
+ tile.height = height
520
+ tile.win.resize(height, width)
521
+ tile.win.mvwin(begin_y, scree_offset[1])
522
+ begin_y += tile.height
523
+ tile.need_draw = True
524
+ self.refresh_all()
525
+
472
526
  def set_status(self):
473
527
  total = 0
474
528
  iteration = 0
@@ -476,10 +530,8 @@ class ProgressBars:
476
530
  for tile_name, tile in self.tiles.items():
477
531
  if tile_name == "status:":
478
532
  continue
479
- if "total" not in tile:
480
- continue
481
- total += tile["total"]
482
- iteration += tile["iteration"]
533
+ total += tile.total
534
+ iteration += tile.iteration
483
535
  if total > 0 and iteration > 0:
484
536
  done_percent = float(iteration) / total * 100
485
537
 
@@ -548,7 +600,7 @@ class ProgressBars:
548
600
  curses.curs_set(0)
549
601
  curses.start_color()
550
602
  self.color_to_curses = init_colors()
551
- self.state = "OK"
603
+ self.state = UiState.OK
552
604
  self.make_tiles()
553
605
  self.progress_length = scree_size[1] // 3
554
606
 
@@ -559,34 +611,33 @@ class ProgressBars:
559
611
  curses.nocbreak()
560
612
  curses.echo()
561
613
  curses.endwin()
562
- self.state = "CLOSED"
614
+ self.state = UiState.CLOSED
563
615
 
564
616
  def draw_content(self, tile_name):
565
617
  tile = self.tiles[tile_name]
566
- win = tile["win"]
618
+ win = tile.win
567
619
  size = win.getmaxyx()
568
620
  margin = 1
569
621
  if (size[0] - 2 * margin) <= 0:
570
622
  return
571
- res = text_term_format.curses_format(tile["content"], "switch_out")
572
- draw_lines_in_win(res, win, color_to_curses=self.color_to_curses, margin=margin)
623
+ draw_lines_in_win(tile.content, win, color_to_curses=self.color_to_curses, margin=margin)
573
624
 
574
625
  def draw_title(self, tile_name):
575
626
  tile = self.tiles[tile_name]
576
- title = tile["title"]
577
- win = tile["win"]
627
+ title = tile.title
628
+ win = tile.win
578
629
  if not isinstance(title, (tuple, list)):
579
630
  title = [title]
580
- draw_lines_in_win({0: title}, win, color_to_curses=self.color_to_curses, x_margin=1)
631
+ draw_lines_in_win([title], win, color_to_curses=self.color_to_curses, x_margin=1)
581
632
 
582
633
  def refresh(self, tile_name: str, noutrefresh: bool = False):
583
634
  # see noutrefresh in curses doc
584
635
  tile = self.tiles[tile_name]
585
- win = tile["win"]
586
- if not tile["need_draw"] or win is None:
636
+ win = tile.win
637
+ if not tile.need_draw or win is None:
587
638
  return
588
639
  win.clear()
589
- if tile["height"] > 1:
640
+ if tile.height > 1:
590
641
  win.border()
591
642
  self.draw_title(tile_name)
592
643
  self.draw_content(tile_name)
@@ -594,12 +645,17 @@ class ProgressBars:
594
645
  win.noutrefresh()
595
646
  else:
596
647
  win.refresh()
597
- tile["need_draw"] = False
648
+ tile.need_draw = False
598
649
 
599
650
  def refresh_all(self):
600
- if self.state != "OK":
651
+ if self.state is UiState.CLOSED:
601
652
  return
602
- self.get_pressed_keys()
653
+ if self.state is UiState.OK:
654
+ ch_list = self.get_pressed_keys()
655
+ if "\t" in ch_list: # Tab
656
+ self._next_active_tile()
657
+ if "\x1b[Z" in ch_list: # shift-Tab
658
+ self._prev_active_tile()
603
659
  self.screen.refresh()
604
660
  self.set_status()
605
661
  tile_name = None
@@ -611,21 +667,36 @@ class ProgressBars:
611
667
  def set_title(self, tile_name, title):
612
668
  tile = self.tiles[tile_name]
613
669
  # в 0 элементе хранится выровненный хостнейм
614
- title0 = tile["title"][0]
615
- new_title = (title0, title)
616
- if new_title == tile["title"]:
670
+ title0 = tile.title[0]
671
+ new_title = [title0, title]
672
+ if new_title == tile.title:
617
673
  return
618
- tile["title"] = new_title
619
- tile["need_draw"] = True
674
+ tile.title = new_title
675
+ tile.need_draw = True
620
676
  if not self.terminal_refresher_coro:
621
677
  self.refresh(tile_name)
622
678
 
623
679
  def set_content(self, tile_name: str, content: str):
624
680
  tile = self.tiles[tile_name]
625
- if content == tile["content"]:
681
+ new_content = list(text_term_format.curses_format(content, "switch_out").values())
682
+ if new_content == tile.content:
626
683
  return
627
- tile["need_draw"] = True
628
- tile["content"] = content
684
+ tile.need_draw = True
685
+ tile.content = new_content
686
+ if not self.terminal_refresher_coro:
687
+ self.refresh(tile_name)
688
+
689
+ def add_content(self, tile_name: str, content: str):
690
+ tile = self.tiles[tile_name]
691
+ tile.need_draw = True
692
+ tile.content.extend(text_term_format.curses_format(content, "switch_out").values())
693
+ if not self.terminal_refresher_coro:
694
+ self.refresh(tile_name)
695
+
696
+ def reset_content(self, tile_name: str):
697
+ tile = self.tiles[tile_name]
698
+ tile.need_draw = True
699
+ tile.content = []
629
700
  if not self.terminal_refresher_coro:
630
701
  self.refresh(tile_name)
631
702
 
@@ -655,50 +726,66 @@ class ProgressBars:
655
726
  bar = fill * filled_length + "-" * (self.progress_length - filled_length)
656
727
  res = "%s |%s| %s%% %s" % (prefix, bar, percent, suffix)
657
728
  tile = self.tiles[tile_name]
658
- tile["total"] = total
659
- tile["iteration"] = iteration
729
+ tile.total = total
730
+ tile.iteration = iteration
660
731
  if error:
661
732
  res = TextArgs(res, "red")
662
733
  else:
663
734
  res = TextArgs(res, "cyan")
664
735
  self.set_title(tile_name, res)
665
736
 
666
- def set_exception(self, fqdn, cmd_exc, last_cmd, progress_max):
737
+ def set_exception(self, tile_name: str, cmd_exc: str, last_cmd: str, progress_max: int, content: str = ""):
667
738
  suffix = "cmd error: %s %s" % (str(cmd_exc).strip().replace("\n", "--"), last_cmd)
668
- self.set_progress(fqdn, progress_max, progress_max, suffix=suffix, error=True)
739
+ self.set_progress(tile_name, progress_max, progress_max, suffix=suffix, error=True)
740
+ if content:
741
+ self.add_content(tile_name, content)
669
742
 
670
743
  def get_pressed_keys(self):
671
- ch_list = []
744
+ ch_list = ""
672
745
  while True:
673
746
  try:
674
747
  ch = self.screen.getkey()
675
- ch_list.append(ch)
748
+ ch_list += ch
676
749
  except Exception:
677
750
  time.sleep(0.01)
678
751
  break
679
752
  return ch_list
680
753
 
681
754
  async def wait_for_exit(self):
755
+ self.state = UiState.WAIT_INPUT
682
756
  while True:
683
757
  ch_list = self.get_pressed_keys()
684
758
  if ch_list:
685
- get_logger().debug("read ch %s", ch_list)
759
+ get_logger().debug("read ch %s", repr(ch_list))
760
+ if "\t" in ch_list: # Tab
761
+ self._next_active_tile()
762
+ if "\x1b[Z" in ch_list: # shift-Tab
763
+ self._prev_active_tile()
686
764
  if "q" in ch_list:
687
765
  return
688
766
  else:
689
767
  await asyncio.sleep(0.001)
690
768
 
691
769
 
692
- def draw_lines_in_win(lines, win, color_to_curses: dict[Optional[str], int], margin=0, x_margin=0, y_margin=0):
770
+ def draw_lines_in_win(
771
+ lines: list[list[TextArgs]],
772
+ win: "curses.window | None",
773
+ color_to_curses: dict[Optional[str], int],
774
+ margin: int = 0,
775
+ x_margin: int = 0,
776
+ y_margin: int = 0,
777
+ ) -> None:
778
+ if win is None:
779
+ return
693
780
  max_y, max_x = win.getmaxyx()
694
781
  max_y -= 2 * (margin or y_margin)
695
782
  max_x -= 2 * (margin or x_margin)
696
- lines_count = max(lines.keys())
783
+ lines_count = len(lines)
697
784
  if lines_count > max_y:
698
785
  start_line = lines_count - max_y + 1
699
786
  else:
700
787
  start_line = 0
701
- for line_no, line_data in sorted(lines.items())[start_line:]:
788
+ for line_no, line_data in enumerate(lines[start_line:]):
702
789
  line_no = line_no - start_line
703
790
  line_pos_calc = 0
704
791
  for line_part in line_data:
@@ -720,55 +807,3 @@ def draw_lines_in_win(lines, win, color_to_curses: dict[Optional[str], int], mar
720
807
  except Exception as exp:
721
808
  get_logger().error("y=%s, x=%s, text=%s %s", y, x, text, exp)
722
809
  line_pos_calc += len(text)
723
-
724
-
725
- def wrap_to_windows(window, text: str, margin: int = 2) -> List[str]:
726
- y, x = window.getmaxyx()
727
- wrapped_text = textwrap.wrap(text, x - margin, max_lines=y, subsequent_indent="+")
728
- return wrapped_text
729
-
730
-
731
- def curses_input(prompt: str, screen, header: Dict[int, str], hide_input=False):
732
- import curses
733
-
734
- if not header:
735
- text = {0: prompt}
736
- else:
737
- text = header.copy()
738
- text[max(header) + 1] = prompt
739
- lines = [line for text_item in text.values() for line in wrap_to_windows(screen, text_item)]
740
- text = dict(enumerate(lines))
741
- maxy, maxx = screen.getmaxyx()
742
- curses.init_pair(12, curses.COLOR_RED, curses.COLOR_WHITE)
743
- input_len = 35
744
- res: list[str] = []
745
- text_width = max([len(x) for x in text.values()] + [len(prompt) + input_len])
746
- header_height = max(list(text) + [0]) + 1
747
- begin_y = int(maxy / 2)
748
- begin_x = int(maxx / 2 - text_width / 2)
749
- nlines = header_height + 2
750
- ncols = text_width + 2
751
- win = curses.newwin(nlines, ncols, begin_y, begin_x)
752
- curses.noecho()
753
- prev_curs = curses.curs_set(1)
754
- while True:
755
- win.clear()
756
- win.refresh()
757
- win.bkgd(curses.color_pair(12))
758
- win.box()
759
- draw_lines_in_win(text, win, margin=1, color_to_curses={})
760
- if hide_input:
761
- win.addstr(header_height, len(prompt) + 1, "*" * len(res))
762
- else:
763
- win.addstr(header_height, len(prompt) + 1, "".join(res))
764
- ch = win.getkey()
765
- if ch == "\x7f":
766
- if res:
767
- res.pop(-1)
768
- elif ch == "\n":
769
- break
770
- else:
771
- res.append(ch)
772
- win.erase()
773
- curses.curs_set(prev_curs)
774
- return "".join(res)
@@ -40,7 +40,7 @@ class SwitchOutputLexer(RegexLexer):
40
40
  "root": [
41
41
  (r"[wW]arning.*\n", Warning),
42
42
  (r"Info.*\n", Warning),
43
- (r"Error.*\n", Error),
43
+ (r"[eE]rror.*\n", Error),
44
44
  (r".*\n", Token.Text),
45
45
  ]
46
46
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 2.1.0
3
+ Version: 2.2.1
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes