pvectl 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (558) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/rules/branch-before-changes.md +52 -0
  3. data/.claude/rules/documentation-updates.md +104 -0
  4. data/.claude/rules/git-workflow.md +84 -0
  5. data/.claude/rules/proxmox-api-docs.md +58 -0
  6. data/.claude/rules/rbs-signatures.md +80 -0
  7. data/.claude/rules/refactoring-as-design-option.md +35 -0
  8. data/.claude/scheduled_tasks.lock +1 -0
  9. data/.claude/settings.json +51 -0
  10. data/.mcp.json +8 -0
  11. data/.ruby-gemset +1 -0
  12. data/.ruby-version +1 -0
  13. data/CHANGELOG.md +138 -0
  14. data/CLAUDE.md +211 -0
  15. data/CODE_OF_CONDUCT.md +132 -0
  16. data/LICENSE.txt +21 -0
  17. data/README.md +143 -0
  18. data/Rakefile +8 -0
  19. data/docs/proxmox-api-update.sh +96 -0
  20. data/exe/pvectl +5 -0
  21. data/lib/pvectl/argv_preprocessor.rb +334 -0
  22. data/lib/pvectl/cli.rb +102 -0
  23. data/lib/pvectl/commands/apt.rb +389 -0
  24. data/lib/pvectl/commands/clone_container.rb +230 -0
  25. data/lib/pvectl/commands/clone_vm.rb +331 -0
  26. data/lib/pvectl/commands/cloudinit/command.rb +122 -0
  27. data/lib/pvectl/commands/cloudinit/dump.rb +94 -0
  28. data/lib/pvectl/commands/cloudinit/pending.rb +137 -0
  29. data/lib/pvectl/commands/cloudinit/regenerate.rb +79 -0
  30. data/lib/pvectl/commands/config/command.rb +65 -0
  31. data/lib/pvectl/commands/config/get_contexts.rb +68 -0
  32. data/lib/pvectl/commands/config/set_cluster.rb +103 -0
  33. data/lib/pvectl/commands/config/set_context.rb +136 -0
  34. data/lib/pvectl/commands/config/set_credentials.rb +181 -0
  35. data/lib/pvectl/commands/config/use_context.rb +69 -0
  36. data/lib/pvectl/commands/config/view.rb +67 -0
  37. data/lib/pvectl/commands/console.rb +93 -0
  38. data/lib/pvectl/commands/console_ct.rb +187 -0
  39. data/lib/pvectl/commands/console_vm.rb +187 -0
  40. data/lib/pvectl/commands/container_lifecycle_command.rb +77 -0
  41. data/lib/pvectl/commands/create_backup.rb +173 -0
  42. data/lib/pvectl/commands/create_container.rb +141 -0
  43. data/lib/pvectl/commands/create_resource_command.rb +244 -0
  44. data/lib/pvectl/commands/create_snapshot.rb +242 -0
  45. data/lib/pvectl/commands/create_vm.rb +267 -0
  46. data/lib/pvectl/commands/delete_backup.rb +139 -0
  47. data/lib/pvectl/commands/delete_command.rb +119 -0
  48. data/lib/pvectl/commands/delete_container.rb +30 -0
  49. data/lib/pvectl/commands/delete_snapshot.rb +248 -0
  50. data/lib/pvectl/commands/delete_vm.rb +127 -0
  51. data/lib/pvectl/commands/describe/command.rb +251 -0
  52. data/lib/pvectl/commands/edit_container.rb +56 -0
  53. data/lib/pvectl/commands/edit_dns.rb +149 -0
  54. data/lib/pvectl/commands/edit_hosts.rb +135 -0
  55. data/lib/pvectl/commands/edit_node.rb +54 -0
  56. data/lib/pvectl/commands/edit_resource_command.rb +180 -0
  57. data/lib/pvectl/commands/edit_vm.rb +154 -0
  58. data/lib/pvectl/commands/edit_volume.rb +189 -0
  59. data/lib/pvectl/commands/feature_command.rb +230 -0
  60. data/lib/pvectl/commands/feature_container.rb +21 -0
  61. data/lib/pvectl/commands/feature_vm.rb +94 -0
  62. data/lib/pvectl/commands/get/command.rb +360 -0
  63. data/lib/pvectl/commands/get/handlers/backups.rb +76 -0
  64. data/lib/pvectl/commands/get/handlers/capabilities.rb +107 -0
  65. data/lib/pvectl/commands/get/handlers/containers.rb +148 -0
  66. data/lib/pvectl/commands/get/handlers/disks.rb +107 -0
  67. data/lib/pvectl/commands/get/handlers/dns.rb +94 -0
  68. data/lib/pvectl/commands/get/handlers/hosts.rb +94 -0
  69. data/lib/pvectl/commands/get/handlers/nodes.rb +162 -0
  70. data/lib/pvectl/commands/get/handlers/services.rb +81 -0
  71. data/lib/pvectl/commands/get/handlers/snapshots.rb +97 -0
  72. data/lib/pvectl/commands/get/handlers/storage.rb +118 -0
  73. data/lib/pvectl/commands/get/handlers/subscription.rb +69 -0
  74. data/lib/pvectl/commands/get/handlers/tasks.rb +89 -0
  75. data/lib/pvectl/commands/get/handlers/templates.rb +175 -0
  76. data/lib/pvectl/commands/get/handlers/time.rb +118 -0
  77. data/lib/pvectl/commands/get/handlers/vms.rb +145 -0
  78. data/lib/pvectl/commands/get/handlers/volume.rb +134 -0
  79. data/lib/pvectl/commands/get/resource_handler.rb +63 -0
  80. data/lib/pvectl/commands/get/resource_registry.rb +18 -0
  81. data/lib/pvectl/commands/get/watch_loop.rb +129 -0
  82. data/lib/pvectl/commands/irreversible_command.rb +265 -0
  83. data/lib/pvectl/commands/logs/command.rb +275 -0
  84. data/lib/pvectl/commands/logs/handlers/journal.rb +46 -0
  85. data/lib/pvectl/commands/logs/handlers/syslog.rb +53 -0
  86. data/lib/pvectl/commands/logs/handlers/task_detail.rb +52 -0
  87. data/lib/pvectl/commands/logs/handlers/task_logs.rb +115 -0
  88. data/lib/pvectl/commands/logs/resource_handler.rb +46 -0
  89. data/lib/pvectl/commands/logs/resource_registry.rb +22 -0
  90. data/lib/pvectl/commands/migrate_command.rb +282 -0
  91. data/lib/pvectl/commands/migrate_container.rb +23 -0
  92. data/lib/pvectl/commands/migrate_vm.rb +122 -0
  93. data/lib/pvectl/commands/move_disk_command.rb +239 -0
  94. data/lib/pvectl/commands/move_disk_container.rb +21 -0
  95. data/lib/pvectl/commands/move_disk_vm.rb +127 -0
  96. data/lib/pvectl/commands/ping.rb +249 -0
  97. data/lib/pvectl/commands/pull.rb +342 -0
  98. data/lib/pvectl/commands/push.rb +352 -0
  99. data/lib/pvectl/commands/reset.rb +64 -0
  100. data/lib/pvectl/commands/resource_lifecycle_command.rb +277 -0
  101. data/lib/pvectl/commands/resource_registry.rb +73 -0
  102. data/lib/pvectl/commands/restart.rb +70 -0
  103. data/lib/pvectl/commands/restart_container.rb +18 -0
  104. data/lib/pvectl/commands/restore_backup.rb +236 -0
  105. data/lib/pvectl/commands/resume.rb +57 -0
  106. data/lib/pvectl/commands/rollback_snapshot.rb +228 -0
  107. data/lib/pvectl/commands/sendkey_vm.rb +205 -0
  108. data/lib/pvectl/commands/service.rb +293 -0
  109. data/lib/pvectl/commands/set_container.rb +50 -0
  110. data/lib/pvectl/commands/set_node.rb +52 -0
  111. data/lib/pvectl/commands/set_resource_command.rb +185 -0
  112. data/lib/pvectl/commands/set_vm.rb +136 -0
  113. data/lib/pvectl/commands/set_volume.rb +212 -0
  114. data/lib/pvectl/commands/shared_config_parsers.rb +126 -0
  115. data/lib/pvectl/commands/shared_flags.rb +155 -0
  116. data/lib/pvectl/commands/shutdown.rb +73 -0
  117. data/lib/pvectl/commands/shutdown_container.rb +18 -0
  118. data/lib/pvectl/commands/start.rb +79 -0
  119. data/lib/pvectl/commands/start_container.rb +18 -0
  120. data/lib/pvectl/commands/stop.rb +75 -0
  121. data/lib/pvectl/commands/stop_container.rb +18 -0
  122. data/lib/pvectl/commands/suspend.rb +64 -0
  123. data/lib/pvectl/commands/template_command.rb +205 -0
  124. data/lib/pvectl/commands/template_container.rb +27 -0
  125. data/lib/pvectl/commands/template_vm.rb +106 -0
  126. data/lib/pvectl/commands/top/command.rb +206 -0
  127. data/lib/pvectl/commands/top/handlers/containers.rb +61 -0
  128. data/lib/pvectl/commands/top/handlers/nodes.rb +61 -0
  129. data/lib/pvectl/commands/top/handlers/vms.rb +61 -0
  130. data/lib/pvectl/commands/top/resource_handler.rb +46 -0
  131. data/lib/pvectl/commands/top/resource_registry.rb +22 -0
  132. data/lib/pvectl/commands/unlink_disk_vm.rb +232 -0
  133. data/lib/pvectl/commands/vm_lifecycle_command.rb +77 -0
  134. data/lib/pvectl/commands/wakeonlan_node.rb +153 -0
  135. data/lib/pvectl/config/errors.rb +62 -0
  136. data/lib/pvectl/config/models/cluster.rb +180 -0
  137. data/lib/pvectl/config/models/context.rb +100 -0
  138. data/lib/pvectl/config/models/resolved_config.rb +171 -0
  139. data/lib/pvectl/config/models/user.rb +133 -0
  140. data/lib/pvectl/config/provider.rb +297 -0
  141. data/lib/pvectl/config/service.rb +300 -0
  142. data/lib/pvectl/config/store.rb +161 -0
  143. data/lib/pvectl/config/wizard.rb +309 -0
  144. data/lib/pvectl/config_serializer.rb +1034 -0
  145. data/lib/pvectl/connection/retry_handler.rb +161 -0
  146. data/lib/pvectl/connection.rb +157 -0
  147. data/lib/pvectl/console/terminal_session.rb +449 -0
  148. data/lib/pvectl/editor_session.rb +157 -0
  149. data/lib/pvectl/exit_codes.rb +43 -0
  150. data/lib/pvectl/formatters/base.rb +55 -0
  151. data/lib/pvectl/formatters/color_support.rb +90 -0
  152. data/lib/pvectl/formatters/json.rb +45 -0
  153. data/lib/pvectl/formatters/output_helper.rb +77 -0
  154. data/lib/pvectl/formatters/registry.rb +72 -0
  155. data/lib/pvectl/formatters/table.rb +235 -0
  156. data/lib/pvectl/formatters/wide.rb +93 -0
  157. data/lib/pvectl/formatters/yaml.rb +49 -0
  158. data/lib/pvectl/manifest_serializer.rb +142 -0
  159. data/lib/pvectl/models/apt_package.rb +107 -0
  160. data/lib/pvectl/models/backup.rb +173 -0
  161. data/lib/pvectl/models/base.rb +49 -0
  162. data/lib/pvectl/models/capability.rb +62 -0
  163. data/lib/pvectl/models/container.rb +205 -0
  164. data/lib/pvectl/models/container_operation_result.rb +27 -0
  165. data/lib/pvectl/models/dns_config.rb +54 -0
  166. data/lib/pvectl/models/hosts_file.rb +47 -0
  167. data/lib/pvectl/models/journal_entry.rb +16 -0
  168. data/lib/pvectl/models/network_interface.rb +85 -0
  169. data/lib/pvectl/models/node.rb +195 -0
  170. data/lib/pvectl/models/node_operation_result.rb +45 -0
  171. data/lib/pvectl/models/operation_result.rb +110 -0
  172. data/lib/pvectl/models/physical_disk.rb +193 -0
  173. data/lib/pvectl/models/service.rb +80 -0
  174. data/lib/pvectl/models/snapshot.rb +101 -0
  175. data/lib/pvectl/models/snapshot_description.rb +39 -0
  176. data/lib/pvectl/models/storage.rb +180 -0
  177. data/lib/pvectl/models/subscription.rb +87 -0
  178. data/lib/pvectl/models/syslog_entry.rb +17 -0
  179. data/lib/pvectl/models/task.rb +95 -0
  180. data/lib/pvectl/models/task_entry.rb +52 -0
  181. data/lib/pvectl/models/task_log_line.rb +17 -0
  182. data/lib/pvectl/models/time_config.rb +47 -0
  183. data/lib/pvectl/models/vm.rb +137 -0
  184. data/lib/pvectl/models/vm_operation_result.rb +27 -0
  185. data/lib/pvectl/models/volume.rb +133 -0
  186. data/lib/pvectl/models/volume_operation_result.rb +26 -0
  187. data/lib/pvectl/parsers/cloud_init_config.rb +92 -0
  188. data/lib/pvectl/parsers/disk_config.rb +97 -0
  189. data/lib/pvectl/parsers/lxc_mount_config.rb +98 -0
  190. data/lib/pvectl/parsers/lxc_net_config.rb +97 -0
  191. data/lib/pvectl/parsers/net_config.rb +95 -0
  192. data/lib/pvectl/parsers/smart_text.rb +42 -0
  193. data/lib/pvectl/plugin_loader.rb +157 -0
  194. data/lib/pvectl/presenters/apt_package.rb +99 -0
  195. data/lib/pvectl/presenters/backup.rb +128 -0
  196. data/lib/pvectl/presenters/base.rb +283 -0
  197. data/lib/pvectl/presenters/capability.rb +104 -0
  198. data/lib/pvectl/presenters/config/context.rb +80 -0
  199. data/lib/pvectl/presenters/container.rb +574 -0
  200. data/lib/pvectl/presenters/container_operation_result.rb +109 -0
  201. data/lib/pvectl/presenters/disk.rb +184 -0
  202. data/lib/pvectl/presenters/dns_config.rb +68 -0
  203. data/lib/pvectl/presenters/hosts_file.rb +61 -0
  204. data/lib/pvectl/presenters/journal_entry.rb +20 -0
  205. data/lib/pvectl/presenters/node.rb +762 -0
  206. data/lib/pvectl/presenters/node_operation_result.rb +50 -0
  207. data/lib/pvectl/presenters/operation_result.rb +61 -0
  208. data/lib/pvectl/presenters/service.rb +76 -0
  209. data/lib/pvectl/presenters/snapshot.rb +239 -0
  210. data/lib/pvectl/presenters/snapshot_operation_result.rb +125 -0
  211. data/lib/pvectl/presenters/storage.rb +329 -0
  212. data/lib/pvectl/presenters/subscription.rb +189 -0
  213. data/lib/pvectl/presenters/syslog_entry.rb +20 -0
  214. data/lib/pvectl/presenters/task_entry.rb +69 -0
  215. data/lib/pvectl/presenters/task_log_line.rb +20 -0
  216. data/lib/pvectl/presenters/template.rb +76 -0
  217. data/lib/pvectl/presenters/time_config.rb +86 -0
  218. data/lib/pvectl/presenters/top_container.rb +112 -0
  219. data/lib/pvectl/presenters/top_node.rb +115 -0
  220. data/lib/pvectl/presenters/top_presenter.rb +59 -0
  221. data/lib/pvectl/presenters/top_vm.rb +105 -0
  222. data/lib/pvectl/presenters/vm.rb +853 -0
  223. data/lib/pvectl/presenters/vm_operation_result.rb +109 -0
  224. data/lib/pvectl/presenters/volume.rb +136 -0
  225. data/lib/pvectl/presenters/volume_operation_result.rb +58 -0
  226. data/lib/pvectl/repositories/apt.rb +93 -0
  227. data/lib/pvectl/repositories/backup.rb +186 -0
  228. data/lib/pvectl/repositories/base.rb +110 -0
  229. data/lib/pvectl/repositories/capabilities.rb +96 -0
  230. data/lib/pvectl/repositories/container.rb +503 -0
  231. data/lib/pvectl/repositories/disk.rb +87 -0
  232. data/lib/pvectl/repositories/dns.rb +54 -0
  233. data/lib/pvectl/repositories/hosts.rb +63 -0
  234. data/lib/pvectl/repositories/journal.rb +23 -0
  235. data/lib/pvectl/repositories/node.rb +537 -0
  236. data/lib/pvectl/repositories/service.rb +139 -0
  237. data/lib/pvectl/repositories/snapshot.rb +133 -0
  238. data/lib/pvectl/repositories/storage.rb +302 -0
  239. data/lib/pvectl/repositories/subscription.rb +77 -0
  240. data/lib/pvectl/repositories/syslog.rb +25 -0
  241. data/lib/pvectl/repositories/task.rb +82 -0
  242. data/lib/pvectl/repositories/task_list.rb +30 -0
  243. data/lib/pvectl/repositories/task_log.rb +31 -0
  244. data/lib/pvectl/repositories/time_config.rb +53 -0
  245. data/lib/pvectl/repositories/vm.rb +616 -0
  246. data/lib/pvectl/repositories/volume.rb +306 -0
  247. data/lib/pvectl/selectors/base.rb +201 -0
  248. data/lib/pvectl/selectors/container.rb +116 -0
  249. data/lib/pvectl/selectors/disk.rb +59 -0
  250. data/lib/pvectl/selectors/vm.rb +116 -0
  251. data/lib/pvectl/selectors/volume.rb +59 -0
  252. data/lib/pvectl/services/backup.rb +209 -0
  253. data/lib/pvectl/services/clone_container.rb +260 -0
  254. data/lib/pvectl/services/clone_vm.rb +265 -0
  255. data/lib/pvectl/services/cloudinit.rb +96 -0
  256. data/lib/pvectl/services/console.rb +152 -0
  257. data/lib/pvectl/services/container_lifecycle.rb +124 -0
  258. data/lib/pvectl/services/create_container.rb +179 -0
  259. data/lib/pvectl/services/create_vm.rb +191 -0
  260. data/lib/pvectl/services/edit_container.rb +125 -0
  261. data/lib/pvectl/services/edit_dns.rb +159 -0
  262. data/lib/pvectl/services/edit_hosts.rb +78 -0
  263. data/lib/pvectl/services/edit_node.rb +147 -0
  264. data/lib/pvectl/services/edit_vm.rb +125 -0
  265. data/lib/pvectl/services/edit_volume.rb +224 -0
  266. data/lib/pvectl/services/get/resource_service.rb +98 -0
  267. data/lib/pvectl/services/move_disk.rb +132 -0
  268. data/lib/pvectl/services/pull_config.rb +94 -0
  269. data/lib/pvectl/services/push_config.rb +524 -0
  270. data/lib/pvectl/services/resize_volume.rb +253 -0
  271. data/lib/pvectl/services/resource_delete.rb +169 -0
  272. data/lib/pvectl/services/resource_migration.rb +170 -0
  273. data/lib/pvectl/services/sendkey.rb +108 -0
  274. data/lib/pvectl/services/service_lifecycle.rb +89 -0
  275. data/lib/pvectl/services/set_container.rb +128 -0
  276. data/lib/pvectl/services/set_node.rb +236 -0
  277. data/lib/pvectl/services/set_vm.rb +128 -0
  278. data/lib/pvectl/services/set_volume.rb +126 -0
  279. data/lib/pvectl/services/snapshot.rb +261 -0
  280. data/lib/pvectl/services/task_listing.rb +75 -0
  281. data/lib/pvectl/services/unlink_disk.rb +86 -0
  282. data/lib/pvectl/services/vm_lifecycle.rb +124 -0
  283. data/lib/pvectl/services/wakeonlan.rb +79 -0
  284. data/lib/pvectl/utils/resource_resolver.rb +80 -0
  285. data/lib/pvectl/version.rb +13 -0
  286. data/lib/pvectl/wizards/create_container.rb +105 -0
  287. data/lib/pvectl/wizards/create_vm.rb +98 -0
  288. data/lib/pvectl.rb +439 -0
  289. data/sig/external/gli.rbs +16 -0
  290. data/sig/external/proxmox_api.rbs +10 -0
  291. data/sig/pvectl/argv_preprocessor.rbs +53 -0
  292. data/sig/pvectl/cli.rbs +26 -0
  293. data/sig/pvectl/commands/apt.rbs +47 -0
  294. data/sig/pvectl/commands/clone_container.rbs +31 -0
  295. data/sig/pvectl/commands/clone_vm.rbs +33 -0
  296. data/sig/pvectl/commands/cloudinit/command.rbs +13 -0
  297. data/sig/pvectl/commands/cloudinit/dump.rbs +13 -0
  298. data/sig/pvectl/commands/cloudinit/pending.rbs +17 -0
  299. data/sig/pvectl/commands/cloudinit/regenerate.rbs +11 -0
  300. data/sig/pvectl/commands/config/command.rbs +9 -0
  301. data/sig/pvectl/commands/config/get_contexts.rbs +11 -0
  302. data/sig/pvectl/commands/config/set_cluster.rbs +11 -0
  303. data/sig/pvectl/commands/config/set_context.rbs +15 -0
  304. data/sig/pvectl/commands/config/set_credentials.rbs +15 -0
  305. data/sig/pvectl/commands/config/use_context.rbs +11 -0
  306. data/sig/pvectl/commands/config/view.rbs +11 -0
  307. data/sig/pvectl/commands/console.rbs +9 -0
  308. data/sig/pvectl/commands/console_ct.rbs +27 -0
  309. data/sig/pvectl/commands/console_vm.rbs +27 -0
  310. data/sig/pvectl/commands/container_lifecycle_command.rbs +25 -0
  311. data/sig/pvectl/commands/create_backup.rbs +29 -0
  312. data/sig/pvectl/commands/create_container.rbs +30 -0
  313. data/sig/pvectl/commands/create_resource_command.rbs +53 -0
  314. data/sig/pvectl/commands/create_snapshot.rbs +35 -0
  315. data/sig/pvectl/commands/create_vm.rbs +30 -0
  316. data/sig/pvectl/commands/delete_backup.rbs +25 -0
  317. data/sig/pvectl/commands/delete_command.rbs +45 -0
  318. data/sig/pvectl/commands/delete_container.rbs +11 -0
  319. data/sig/pvectl/commands/delete_snapshot.rbs +35 -0
  320. data/sig/pvectl/commands/delete_vm.rbs +13 -0
  321. data/sig/pvectl/commands/describe/command.rbs +27 -0
  322. data/sig/pvectl/commands/edit_container.rbs +17 -0
  323. data/sig/pvectl/commands/edit_dns.rbs +25 -0
  324. data/sig/pvectl/commands/edit_hosts.rbs +23 -0
  325. data/sig/pvectl/commands/edit_node.rbs +17 -0
  326. data/sig/pvectl/commands/edit_resource_command.rbs +35 -0
  327. data/sig/pvectl/commands/edit_vm.rbs +19 -0
  328. data/sig/pvectl/commands/edit_volume.rbs +24 -0
  329. data/sig/pvectl/commands/feature_command.rbs +43 -0
  330. data/sig/pvectl/commands/feature_container.rbs +10 -0
  331. data/sig/pvectl/commands/feature_vm.rbs +12 -0
  332. data/sig/pvectl/commands/get/command.rbs +42 -0
  333. data/sig/pvectl/commands/get/handlers/backups.rbs +23 -0
  334. data/sig/pvectl/commands/get/handlers/capabilities.rbs +29 -0
  335. data/sig/pvectl/commands/get/handlers/containers.rbs +35 -0
  336. data/sig/pvectl/commands/get/handlers/disks.rbs +27 -0
  337. data/sig/pvectl/commands/get/handlers/dns.rbs +25 -0
  338. data/sig/pvectl/commands/get/handlers/hosts.rbs +25 -0
  339. data/sig/pvectl/commands/get/handlers/nodes.rbs +33 -0
  340. data/sig/pvectl/commands/get/handlers/services.rbs +23 -0
  341. data/sig/pvectl/commands/get/handlers/snapshots.rbs +27 -0
  342. data/sig/pvectl/commands/get/handlers/storage.rbs +25 -0
  343. data/sig/pvectl/commands/get/handlers/subscription.rbs +25 -0
  344. data/sig/pvectl/commands/get/handlers/tasks.rbs +28 -0
  345. data/sig/pvectl/commands/get/handlers/templates.rbs +35 -0
  346. data/sig/pvectl/commands/get/handlers/time.rbs +29 -0
  347. data/sig/pvectl/commands/get/handlers/vms.rbs +35 -0
  348. data/sig/pvectl/commands/get/handlers/volume.rbs +27 -0
  349. data/sig/pvectl/commands/get/resource_handler.rbs +13 -0
  350. data/sig/pvectl/commands/get/resource_registry.rbs +8 -0
  351. data/sig/pvectl/commands/get/watch_loop.rbs +33 -0
  352. data/sig/pvectl/commands/irreversible_command.rbs +32 -0
  353. data/sig/pvectl/commands/logs/command.rbs +35 -0
  354. data/sig/pvectl/commands/logs/handlers/journal.rbs +21 -0
  355. data/sig/pvectl/commands/logs/handlers/syslog.rbs +21 -0
  356. data/sig/pvectl/commands/logs/handlers/task_detail.rbs +21 -0
  357. data/sig/pvectl/commands/logs/handlers/task_logs.rbs +35 -0
  358. data/sig/pvectl/commands/logs/resource_handler.rbs +11 -0
  359. data/sig/pvectl/commands/logs/resource_registry.rbs +8 -0
  360. data/sig/pvectl/commands/migrate_command.rbs +45 -0
  361. data/sig/pvectl/commands/migrate_container.rbs +11 -0
  362. data/sig/pvectl/commands/migrate_vm.rbs +13 -0
  363. data/sig/pvectl/commands/move_disk_command.rbs +43 -0
  364. data/sig/pvectl/commands/move_disk_container.rbs +11 -0
  365. data/sig/pvectl/commands/move_disk_vm.rbs +13 -0
  366. data/sig/pvectl/commands/ping.rbs +39 -0
  367. data/sig/pvectl/commands/pull.rbs +33 -0
  368. data/sig/pvectl/commands/push.rbs +32 -0
  369. data/sig/pvectl/commands/reset.rbs +11 -0
  370. data/sig/pvectl/commands/resource_lifecycle_command.rbs +55 -0
  371. data/sig/pvectl/commands/resource_registry.rbs +19 -0
  372. data/sig/pvectl/commands/restart.rbs +11 -0
  373. data/sig/pvectl/commands/restart_container.rbs +9 -0
  374. data/sig/pvectl/commands/restore_backup.rbs +27 -0
  375. data/sig/pvectl/commands/resume.rbs +11 -0
  376. data/sig/pvectl/commands/rollback_snapshot.rbs +31 -0
  377. data/sig/pvectl/commands/sendkey_vm.rbs +25 -0
  378. data/sig/pvectl/commands/service.rbs +38 -0
  379. data/sig/pvectl/commands/set_container.rbs +13 -0
  380. data/sig/pvectl/commands/set_node.rbs +13 -0
  381. data/sig/pvectl/commands/set_resource_command.rbs +25 -0
  382. data/sig/pvectl/commands/set_vm.rbs +15 -0
  383. data/sig/pvectl/commands/set_volume.rbs +24 -0
  384. data/sig/pvectl/commands/shared_config_parsers.rbs +19 -0
  385. data/sig/pvectl/commands/shared_flags.rbs +10 -0
  386. data/sig/pvectl/commands/shutdown.rbs +11 -0
  387. data/sig/pvectl/commands/shutdown_container.rbs +9 -0
  388. data/sig/pvectl/commands/start.rbs +11 -0
  389. data/sig/pvectl/commands/start_container.rbs +9 -0
  390. data/sig/pvectl/commands/stop.rbs +11 -0
  391. data/sig/pvectl/commands/stop_container.rbs +9 -0
  392. data/sig/pvectl/commands/suspend.rbs +11 -0
  393. data/sig/pvectl/commands/template_command.rbs +21 -0
  394. data/sig/pvectl/commands/template_container.rbs +10 -0
  395. data/sig/pvectl/commands/template_vm.rbs +12 -0
  396. data/sig/pvectl/commands/top/command.rbs +31 -0
  397. data/sig/pvectl/commands/top/handlers/containers.rbs +21 -0
  398. data/sig/pvectl/commands/top/handlers/nodes.rbs +21 -0
  399. data/sig/pvectl/commands/top/handlers/vms.rbs +21 -0
  400. data/sig/pvectl/commands/top/resource_handler.rbs +11 -0
  401. data/sig/pvectl/commands/top/resource_registry.rbs +8 -0
  402. data/sig/pvectl/commands/unlink_disk_vm.rbs +27 -0
  403. data/sig/pvectl/commands/vm_lifecycle_command.rbs +25 -0
  404. data/sig/pvectl/commands/wakeonlan_node.rbs +21 -0
  405. data/sig/pvectl/config/errors.rbs +24 -0
  406. data/sig/pvectl/config/models/cluster.rbs +39 -0
  407. data/sig/pvectl/config/models/context.rbs +23 -0
  408. data/sig/pvectl/config/models/resolved_config.rbs +51 -0
  409. data/sig/pvectl/config/models/user.rbs +31 -0
  410. data/sig/pvectl/config/provider.rbs +40 -0
  411. data/sig/pvectl/config/service.rbs +65 -0
  412. data/sig/pvectl/config/store.rbs +14 -0
  413. data/sig/pvectl/config/wizard.rbs +48 -0
  414. data/sig/pvectl/config_serializer.rbs +121 -0
  415. data/sig/pvectl/connection/retry_handler.rbs +31 -0
  416. data/sig/pvectl/connection.rbs +35 -0
  417. data/sig/pvectl/console/terminal_session.rbs +63 -0
  418. data/sig/pvectl/editor_session.rbs +33 -0
  419. data/sig/pvectl/exit_codes.rbs +19 -0
  420. data/sig/pvectl/formatters/base.rbs +13 -0
  421. data/sig/pvectl/formatters/color_support.rbs +13 -0
  422. data/sig/pvectl/formatters/json.rbs +7 -0
  423. data/sig/pvectl/formatters/output_helper.rbs +9 -0
  424. data/sig/pvectl/formatters/registry.rbs +13 -0
  425. data/sig/pvectl/formatters/table.rbs +25 -0
  426. data/sig/pvectl/formatters/wide.rbs +15 -0
  427. data/sig/pvectl/formatters/yaml.rbs +7 -0
  428. data/sig/pvectl/manifest_serializer.rbs +18 -0
  429. data/sig/pvectl/models/apt_package.rbs +26 -0
  430. data/sig/pvectl/models/backup.rbs +31 -0
  431. data/sig/pvectl/models/base.rbs +11 -0
  432. data/sig/pvectl/models/capability.rbs +16 -0
  433. data/sig/pvectl/models/container.rbs +44 -0
  434. data/sig/pvectl/models/container_operation_result.rbs +9 -0
  435. data/sig/pvectl/models/dns_config.rbs +15 -0
  436. data/sig/pvectl/models/hosts_file.rbs +13 -0
  437. data/sig/pvectl/models/journal_entry.rbs +10 -0
  438. data/sig/pvectl/models/network_interface.rbs +20 -0
  439. data/sig/pvectl/models/node.rbs +47 -0
  440. data/sig/pvectl/models/node_operation_result.rbs +12 -0
  441. data/sig/pvectl/models/operation_result.rbs +21 -0
  442. data/sig/pvectl/models/physical_disk.rbs +35 -0
  443. data/sig/pvectl/models/service.rbs +18 -0
  444. data/sig/pvectl/models/snapshot.rbs +21 -0
  445. data/sig/pvectl/models/snapshot_description.rbs +18 -0
  446. data/sig/pvectl/models/storage.rbs +39 -0
  447. data/sig/pvectl/models/subscription.rbs +24 -0
  448. data/sig/pvectl/models/syslog_entry.rbs +10 -0
  449. data/sig/pvectl/models/task.rbs +22 -0
  450. data/sig/pvectl/models/task_entry.rbs +24 -0
  451. data/sig/pvectl/models/task_log_line.rbs +10 -0
  452. data/sig/pvectl/models/time_config.rbs +12 -0
  453. data/sig/pvectl/models/vm.rbs +32 -0
  454. data/sig/pvectl/models/vm_operation_result.rbs +9 -0
  455. data/sig/pvectl/models/volume.rbs +29 -0
  456. data/sig/pvectl/models/volume_operation_result.rbs +9 -0
  457. data/sig/pvectl/parsers/cloud_init_config.rbs +15 -0
  458. data/sig/pvectl/parsers/disk_config.rbs +19 -0
  459. data/sig/pvectl/parsers/lxc_mount_config.rbs +19 -0
  460. data/sig/pvectl/parsers/lxc_net_config.rbs +19 -0
  461. data/sig/pvectl/parsers/net_config.rbs +19 -0
  462. data/sig/pvectl/parsers/smart_text.rbs +7 -0
  463. data/sig/pvectl/plugin_loader.rbs +25 -0
  464. data/sig/pvectl/presenters/apt_package.rbs +19 -0
  465. data/sig/pvectl/presenters/backup.rbs +25 -0
  466. data/sig/pvectl/presenters/base.rbs +41 -0
  467. data/sig/pvectl/presenters/capability.rbs +19 -0
  468. data/sig/pvectl/presenters/config/context.rbs +17 -0
  469. data/sig/pvectl/presenters/container.rbs +78 -0
  470. data/sig/pvectl/presenters/container_operation_result.rbs +19 -0
  471. data/sig/pvectl/presenters/disk.rbs +31 -0
  472. data/sig/pvectl/presenters/dns_config.rbs +13 -0
  473. data/sig/pvectl/presenters/hosts_file.rbs +13 -0
  474. data/sig/pvectl/presenters/journal_entry.rbs +11 -0
  475. data/sig/pvectl/presenters/node.rbs +118 -0
  476. data/sig/pvectl/presenters/node_operation_result.rbs +11 -0
  477. data/sig/pvectl/presenters/operation_result.rbs +17 -0
  478. data/sig/pvectl/presenters/service.rbs +15 -0
  479. data/sig/pvectl/presenters/snapshot.rbs +35 -0
  480. data/sig/pvectl/presenters/snapshot_operation_result.rbs +27 -0
  481. data/sig/pvectl/presenters/storage.rbs +59 -0
  482. data/sig/pvectl/presenters/subscription.rbs +36 -0
  483. data/sig/pvectl/presenters/syslog_entry.rbs +11 -0
  484. data/sig/pvectl/presenters/task_entry.rbs +21 -0
  485. data/sig/pvectl/presenters/task_log_line.rbs +11 -0
  486. data/sig/pvectl/presenters/template.rbs +15 -0
  487. data/sig/pvectl/presenters/time_config.rbs +19 -0
  488. data/sig/pvectl/presenters/top_container.rbs +17 -0
  489. data/sig/pvectl/presenters/top_node.rbs +17 -0
  490. data/sig/pvectl/presenters/top_presenter.rbs +13 -0
  491. data/sig/pvectl/presenters/top_vm.rbs +17 -0
  492. data/sig/pvectl/presenters/vm.rbs +91 -0
  493. data/sig/pvectl/presenters/vm_operation_result.rbs +19 -0
  494. data/sig/pvectl/presenters/volume.rbs +23 -0
  495. data/sig/pvectl/presenters/volume_operation_result.rbs +11 -0
  496. data/sig/pvectl/repositories/apt.rbs +17 -0
  497. data/sig/pvectl/repositories/backup.rbs +27 -0
  498. data/sig/pvectl/repositories/base.rbs +23 -0
  499. data/sig/pvectl/repositories/capabilities.rbs +20 -0
  500. data/sig/pvectl/repositories/container.rbs +63 -0
  501. data/sig/pvectl/repositories/disk.rbs +17 -0
  502. data/sig/pvectl/repositories/dns.rbs +13 -0
  503. data/sig/pvectl/repositories/hosts.rbs +13 -0
  504. data/sig/pvectl/repositories/journal.rbs +7 -0
  505. data/sig/pvectl/repositories/node.rbs +68 -0
  506. data/sig/pvectl/repositories/service.rbs +27 -0
  507. data/sig/pvectl/repositories/snapshot.rbs +19 -0
  508. data/sig/pvectl/repositories/storage.rbs +37 -0
  509. data/sig/pvectl/repositories/subscription.rbs +17 -0
  510. data/sig/pvectl/repositories/syslog.rbs +7 -0
  511. data/sig/pvectl/repositories/task.rbs +19 -0
  512. data/sig/pvectl/repositories/task_list.rbs +7 -0
  513. data/sig/pvectl/repositories/task_log.rbs +11 -0
  514. data/sig/pvectl/repositories/time_config.rbs +13 -0
  515. data/sig/pvectl/repositories/vm.rbs +85 -0
  516. data/sig/pvectl/repositories/volume.rbs +43 -0
  517. data/sig/pvectl/selectors/base.rbs +37 -0
  518. data/sig/pvectl/selectors/container.rbs +19 -0
  519. data/sig/pvectl/selectors/disk.rbs +13 -0
  520. data/sig/pvectl/selectors/vm.rbs +19 -0
  521. data/sig/pvectl/selectors/volume.rbs +13 -0
  522. data/sig/pvectl/services/backup.rbs +27 -0
  523. data/sig/pvectl/services/clone_container.rbs +35 -0
  524. data/sig/pvectl/services/clone_vm.rbs +35 -0
  525. data/sig/pvectl/services/cloudinit.rbs +19 -0
  526. data/sig/pvectl/services/console.rbs +23 -0
  527. data/sig/pvectl/services/container_lifecycle.rbs +26 -0
  528. data/sig/pvectl/services/create_container.rbs +64 -0
  529. data/sig/pvectl/services/create_vm.rbs +72 -0
  530. data/sig/pvectl/services/edit_container.rbs +17 -0
  531. data/sig/pvectl/services/edit_dns.rbs +23 -0
  532. data/sig/pvectl/services/edit_hosts.rbs +13 -0
  533. data/sig/pvectl/services/edit_node.rbs +21 -0
  534. data/sig/pvectl/services/edit_vm.rbs +17 -0
  535. data/sig/pvectl/services/edit_volume.rbs +18 -0
  536. data/sig/pvectl/services/get/resource_service.rbs +23 -0
  537. data/sig/pvectl/services/move_disk.rbs +21 -0
  538. data/sig/pvectl/services/pull_config.rbs +18 -0
  539. data/sig/pvectl/services/push_config.rbs +37 -0
  540. data/sig/pvectl/services/resize_volume.rbs +47 -0
  541. data/sig/pvectl/services/resource_delete.rbs +27 -0
  542. data/sig/pvectl/services/resource_migration.rbs +29 -0
  543. data/sig/pvectl/services/sendkey.rbs +19 -0
  544. data/sig/pvectl/services/service_lifecycle.rbs +17 -0
  545. data/sig/pvectl/services/set_container.rbs +14 -0
  546. data/sig/pvectl/services/set_node.rbs +23 -0
  547. data/sig/pvectl/services/set_vm.rbs +14 -0
  548. data/sig/pvectl/services/set_volume.rbs +12 -0
  549. data/sig/pvectl/services/snapshot.rbs +35 -0
  550. data/sig/pvectl/services/task_listing.rbs +13 -0
  551. data/sig/pvectl/services/unlink_disk.rbs +17 -0
  552. data/sig/pvectl/services/vm_lifecycle.rbs +26 -0
  553. data/sig/pvectl/services/wakeonlan.rbs +17 -0
  554. data/sig/pvectl/utils/resource_resolver.rbs +17 -0
  555. data/sig/pvectl/wizards/create_container.rbs +21 -0
  556. data/sig/pvectl/wizards/create_vm.rbs +21 -0
  557. data/sig/pvectl.rbs +9 -0
  558. metadata +675 -0
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Formatters
5
+ # Manages color output based on TTY detection and user flags.
6
+ #
7
+ # Implements color flag priority:
8
+ # 1. --no-color flag (highest) -> disabled
9
+ # 2. --color flag -> enabled
10
+ # 3. NO_COLOR env var -> disabled (see https://no-color.org/)
11
+ # 4. TTY detection (lowest) -> $stdout.tty?
12
+ #
13
+ # @example Check if colors are enabled
14
+ # ColorSupport.enabled?(explicit_flag: nil) # TTY auto-detect
15
+ # ColorSupport.enabled?(explicit_flag: true) # forced on
16
+ # ColorSupport.enabled?(explicit_flag: false) # forced off
17
+ #
18
+ # @example Get Pastel instance
19
+ # pastel = ColorSupport.pastel(explicit_flag: global_options[:color])
20
+ # puts pastel.green("Success!")
21
+ #
22
+ # @example Colorize status value
23
+ # pastel = ColorSupport.pastel(explicit_flag: true)
24
+ # ColorSupport.colorize_status("running", pastel) #=> "\e[32mrunning\e[0m"
25
+ #
26
+ module ColorSupport
27
+ # Status color mapping following kubectl conventions.
28
+ # @return [Hash<String, Symbol>] mapping of status to Pastel color method
29
+ STATUS_COLORS = {
30
+ "running" => :green,
31
+ "stopped" => :red,
32
+ "paused" => :yellow
33
+ }.freeze
34
+
35
+ class << self
36
+ # Determines if color output should be enabled.
37
+ #
38
+ # Priority order:
39
+ # 1. explicit_flag: false (--no-color) -> disabled
40
+ # 2. explicit_flag: true (--color) -> enabled
41
+ # 3. NO_COLOR env var present -> disabled
42
+ # 4. TTY detection -> $stdout.tty?
43
+ #
44
+ # @param explicit_flag [Boolean, nil] value from --color / --no-color flag
45
+ # - true: --color was passed
46
+ # - false: --no-color was passed
47
+ # - nil: no flag passed, use auto-detection
48
+ # @return [Boolean] true if colors should be used
49
+ def enabled?(explicit_flag: nil)
50
+ return false if explicit_flag == false
51
+ return true if explicit_flag == true
52
+ return false if ENV.key?("NO_COLOR")
53
+
54
+ $stdout.tty?
55
+ end
56
+
57
+ # Returns a Pastel instance configured based on color settings.
58
+ #
59
+ # @param explicit_flag [Boolean, nil] value from --color / --no-color flag
60
+ # @return [Pastel] pastel instance with appropriate enabled state
61
+ #
62
+ # @example
63
+ # pastel = ColorSupport.pastel(explicit_flag: true)
64
+ # pastel.green("text") #=> "\e[32mtext\e[0m"
65
+ def pastel(explicit_flag: nil)
66
+ require "pastel"
67
+ Pastel.new(enabled: enabled?(explicit_flag: explicit_flag))
68
+ end
69
+
70
+ # Colors status text according to kubectl conventions.
71
+ #
72
+ # Status colors:
73
+ # - running -> green
74
+ # - stopped -> red
75
+ # - paused -> yellow
76
+ # - unknown status -> dim (gray)
77
+ #
78
+ # @param status [String, nil] status value
79
+ # @param pastel_instance [Pastel] pastel instance
80
+ # @return [String] colored text (or "-" if nil, or dim if unknown status)
81
+ def colorize_status(status, pastel_instance)
82
+ return "-" if status.nil?
83
+
84
+ color = STATUS_COLORS[status.to_s.downcase]
85
+ color ? pastel_instance.public_send(color, status) : pastel_instance.dim(status)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Pvectl
6
+ module Formatters
7
+ # Formats data as JSON output.
8
+ #
9
+ # Collections are rendered as JSON arrays.
10
+ # Single resources are rendered as JSON objects.
11
+ # Empty collections return "[]".
12
+ # Nil values are rendered as JSON null.
13
+ #
14
+ # @example JSON output for collection
15
+ # [
16
+ # {"name": "vm-100", "status": "running"},
17
+ # {"name": "vm-101", "status": "stopped"}
18
+ # ]
19
+ #
20
+ # @example JSON output for single resource
21
+ # {"name": "vm-100", "status": "running", "node": "pve1"}
22
+ #
23
+ class Json < Base
24
+ # Formats data as JSON output.
25
+ #
26
+ # @param data [Array, Object] collection of models or single model
27
+ # @param presenter [Presenters::Base] presenter for hash conversion
28
+ # @param color_enabled [Boolean] ignored for JSON (always plain text)
29
+ # @param describe [Boolean] whether this is a describe command
30
+ # @param context [Hash] ignored for JSON output
31
+ # @return [String] formatted JSON string (pretty-printed)
32
+ def format(data, presenter, color_enabled: true, describe: false, **context)
33
+ if describe && !collection?(data)
34
+ # Use to_description for describe mode
35
+ JSON.pretty_generate(presenter.to_description(data))
36
+ elsif collection?(data)
37
+ hashes = data.map { |model| presenter.to_hash(model) }
38
+ JSON.pretty_generate(hashes)
39
+ else
40
+ JSON.pretty_generate(presenter.to_hash(data))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Formatters
5
+ # Facade for output formatting in commands.
6
+ #
7
+ # Coordinates Formatter and Presenter to produce formatted output.
8
+ # Handles color flag interpretation and prints to stdout.
9
+ #
10
+ # @example Usage in a command
11
+ # def self.execute(global_options)
12
+ # service = Pvectl::Services::Vm.new
13
+ # vms = service.list
14
+ # presenter = Pvectl::Presenters::Vm.new
15
+ #
16
+ # OutputHelper.print(
17
+ # data: vms,
18
+ # presenter: presenter,
19
+ # format: global_options[:output],
20
+ # color_flag: global_options[:color]
21
+ # )
22
+ # end
23
+ #
24
+ # @example Rendering without printing (for testing)
25
+ # output = OutputHelper.render(
26
+ # data: vms,
27
+ # presenter: presenter,
28
+ # format: "json"
29
+ # )
30
+ #
31
+ module OutputHelper
32
+ class << self
33
+ # Formats data and prints to stdout.
34
+ #
35
+ # @param data [Array, Object] collection of models or single model
36
+ # @param presenter [Presenters::Base] presenter for the resource type
37
+ # @param format [String] output format ("table", "json", "yaml", "wide")
38
+ # @param color_flag [Boolean, nil] color flag from CLI
39
+ # - true: --color was passed
40
+ # - false: --no-color was passed
41
+ # - nil: auto-detect based on TTY
42
+ # @param describe [Boolean] whether this is a describe command (default: false)
43
+ # @param context [Hash] additional context passed to presenter
44
+ # @return [void]
45
+ def print(data:, presenter:, format: "table", color_flag: nil, describe: false, **context)
46
+ output = render(data: data, presenter: presenter, format: format, color_flag: color_flag, describe: describe, **context)
47
+ puts output
48
+ end
49
+
50
+ # Returns formatted string without printing.
51
+ #
52
+ # Useful for testing or when you need to manipulate the output
53
+ # before displaying.
54
+ #
55
+ # @param data [Array, Object] collection of models or single model
56
+ # @param presenter [Presenters::Base] presenter for the resource type
57
+ # @param format [String] output format ("table", "json", "yaml", "wide")
58
+ # @param color_flag [Boolean, nil] color flag from CLI
59
+ # @param describe [Boolean] whether this is a describe command
60
+ # @param context [Hash] additional context passed to presenter
61
+ # @return [String] formatted output
62
+ def render(data:, presenter:, format: "table", color_flag: nil, describe: false, **context)
63
+ formatter = Registry.for(format)
64
+ color_enabled = ColorSupport.enabled?(explicit_flag: color_flag)
65
+
66
+ formatter.format(
67
+ data,
68
+ presenter,
69
+ color_enabled: color_enabled,
70
+ describe: describe,
71
+ **context
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Formatters
5
+ # Registry for looking up formatters by name.
6
+ #
7
+ # Implements the Registry Pattern to map format names
8
+ # ("table", "json", "yaml", "wide") to formatter classes.
9
+ #
10
+ # @example Getting a formatter
11
+ # formatter = Registry.for("json")
12
+ # output = formatter.format(data, presenter)
13
+ #
14
+ # @example Listing available formats
15
+ # Registry.available_formats #=> ["table", "wide", "json", "yaml"]
16
+ #
17
+ # @example Checking if format is supported
18
+ # Registry.supported?("json") #=> true
19
+ # Registry.supported?("xml") #=> false
20
+ #
21
+ class Registry
22
+ # Mapping of format names to formatter classes.
23
+ # @return [Hash<String, Class>] frozen hash of format name to class
24
+ FORMATS = {
25
+ "table" => Table,
26
+ "wide" => Wide,
27
+ "json" => Json,
28
+ "yaml" => Yaml
29
+ }.freeze
30
+
31
+ class << self
32
+ # Gets a formatter instance for the specified format.
33
+ #
34
+ # @param format_name [String, Symbol] format name (table, wide, json, yaml)
35
+ # @return [Base] formatter instance
36
+ # @raise [ArgumentError] if format is not found
37
+ #
38
+ # @example
39
+ # formatter = Registry.for("json")
40
+ # formatter.format(data, presenter)
41
+ def for(format_name)
42
+ formatter_class = FORMATS[format_name.to_s]
43
+ raise ArgumentError, "Unknown format: #{format_name}" unless formatter_class
44
+
45
+ formatter_class.new
46
+ end
47
+
48
+ # Returns list of available format names.
49
+ #
50
+ # @return [Array<String>] available format names
51
+ #
52
+ # @example
53
+ # Registry.available_formats #=> ["table", "wide", "json", "yaml"]
54
+ def available_formats
55
+ FORMATS.keys
56
+ end
57
+
58
+ # Checks if a format is supported.
59
+ #
60
+ # @param format_name [String, Symbol] format name
61
+ # @return [Boolean] true if format is supported
62
+ #
63
+ # @example
64
+ # Registry.supported?("json") #=> true
65
+ # Registry.supported?("xml") #=> false
66
+ def supported?(format_name)
67
+ FORMATS.key?(format_name.to_s)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-table"
4
+
5
+ module Pvectl
6
+ module Formatters
7
+ # Formats data as a table using tty-table gem.
8
+ #
9
+ # For collections: renders standard horizontal table with headers.
10
+ # For single resources (describe): renders vertical key-value layout.
11
+ #
12
+ # @example Table output for collection
13
+ # NAME STATUS NODE
14
+ # vm-100 running pve1
15
+ # vm-101 stopped pve2
16
+ #
17
+ # @example Vertical output for single resource (describe)
18
+ # Name: vm-100
19
+ # Status: running
20
+ # Node: pve1
21
+ #
22
+ # @see Pvectl::Formatters::Wide for extended column output
23
+ #
24
+ class Table < Base
25
+ # Formats data as table output.
26
+ #
27
+ # @param data [Array, Object] collection of models or single model
28
+ # @param presenter [Presenters::Base] presenter for column/row definitions
29
+ # @param color_enabled [Boolean] whether to apply color formatting
30
+ # @param describe [Boolean] whether this is a describe command (single resource)
31
+ # @param context [Hash] additional context passed to presenter
32
+ # @return [String] formatted table string
33
+ def format(data, presenter, color_enabled: true, describe: false, **context)
34
+ pastel = ColorSupport.pastel(explicit_flag: color_enabled)
35
+
36
+ if describe || !collection?(data)
37
+ format_describe(data, presenter, pastel)
38
+ else
39
+ format_table(data, presenter, pastel, **context)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Formats collection as horizontal table.
46
+ #
47
+ # @param data [Array] collection of models
48
+ # @param presenter [Presenters::Base] presenter
49
+ # @param pastel [Pastel] pastel instance for coloring
50
+ # @param context [Hash] context passed to presenter
51
+ # @return [String] formatted table
52
+ def format_table(data, presenter, pastel, **context)
53
+ headers = presenter.columns
54
+ rows = data.map do |model|
55
+ row = presenter.to_row(model, **context)
56
+ colorize_row(row, headers, pastel)
57
+ end
58
+
59
+ render_table(headers, rows)
60
+ end
61
+
62
+ # Formats single resource as vertical key-value layout.
63
+ # Supports nested Hashes (sections) and Arrays of Hashes (tables).
64
+ #
65
+ # @param model [Object] single model
66
+ # @param presenter [Presenters::Base] presenter
67
+ # @param pastel [Pastel] pastel instance for coloring
68
+ # @return [String] formatted vertical layout
69
+ def format_describe(model, presenter, pastel)
70
+ hash = presenter.to_description(model)
71
+ format_describe_hash(hash, pastel, indent: 0)
72
+ end
73
+
74
+ # Colorizes status columns in a row.
75
+ #
76
+ # @param row [Array] row values
77
+ # @param headers [Array<String>] column headers
78
+ # @param pastel [Pastel] pastel instance
79
+ # @return [Array] row with colorized values
80
+ def colorize_row(row, headers, pastel)
81
+ row.each_with_index.map do |value, idx|
82
+ header = headers[idx].to_s.downcase
83
+ if header == "status"
84
+ ColorSupport.colorize_status(value, pastel)
85
+ else
86
+ normalize_nil(value)
87
+ end
88
+ end
89
+ end
90
+
91
+ # Recursively formats a hash for describe output.
92
+ #
93
+ # At indent 0 (top level), once a section (Hash/Array) has been rendered,
94
+ # all subsequent entries get a blank line separator for consistent spacing.
95
+ #
96
+ # @param hash [Hash] hash to format
97
+ # @param pastel [Pastel] pastel instance
98
+ # @param indent [Integer] current indentation level
99
+ # @return [String] formatted output
100
+ def format_describe_hash(hash, pastel, indent: 0)
101
+ return "-" if hash.nil?
102
+
103
+ lines = []
104
+ prefix = " " * indent
105
+
106
+ # Calculate max key length for alignment (only for non-nested values)
107
+ simple_keys = hash.select { |_, v| !v.is_a?(Hash) && !(v.is_a?(Array) && v.first.is_a?(Hash)) && !v.to_s.include?("\n") }
108
+ max_key_length = simple_keys.keys.map { |k| k.to_s.length }.max || 0
109
+
110
+ # Track whether we've passed a nested section (Hash/Array).
111
+ # Once a section appears, all subsequent simple entries get blank-line separators.
112
+ prev_was_section = false
113
+
114
+ hash.each do |key, value|
115
+ human_key = humanize_key(key)
116
+
117
+ if value.is_a?(Hash)
118
+ # Nested section
119
+ lines << ""
120
+ lines << "#{prefix}#{human_key}:"
121
+ lines << format_describe_hash(value, pastel, indent: indent + 1)
122
+ prev_was_section = true
123
+ elsif value.is_a?(Array) && !value.empty? && value.first.is_a?(Hash)
124
+ # Array of hashes -> inline table
125
+ lines << ""
126
+ lines << "#{prefix}#{human_key}:"
127
+ lines << format_describe_table(value, indent: indent + 1)
128
+ prev_was_section = true
129
+ elsif value.is_a?(Array) && value.empty?
130
+ # Empty array -> show as "-"
131
+ lines << "" if prev_was_section
132
+ formatted_key = "#{human_key}:".ljust(max_key_length + 2)
133
+ lines << "#{prefix}#{formatted_key}-"
134
+ else
135
+ formatted_value = format_describe_value(value, key.to_s, pastel)
136
+ if formatted_value.include?("\n")
137
+ # Multi-line value: render as block section
138
+ lines << ""
139
+ lines << "#{prefix}#{human_key}:"
140
+ formatted_value.each_line do |line|
141
+ lines << "#{prefix} #{line.chomp}"
142
+ end
143
+ prev_was_section = true
144
+ else
145
+ # Simple key-value — add separator after sections at any indent level
146
+ lines << "" if prev_was_section
147
+ formatted_key = "#{human_key}:".ljust(max_key_length + 2)
148
+ lines << "#{prefix}#{formatted_key}#{formatted_value}"
149
+ end
150
+ end
151
+ end
152
+
153
+ lines.join("\n").sub(/\A\n+/, "").rstrip
154
+ end
155
+
156
+ # Formats array of hashes as inline table.
157
+ #
158
+ # @param array [Array<Hash>] array of hashes
159
+ # @param indent [Integer] indentation level
160
+ # @return [String] formatted table
161
+ def format_describe_table(array, indent: 0)
162
+ return " " * indent + "-" if array.empty?
163
+
164
+ prefix = " " * indent
165
+ headers = array.first.keys.map { |k| k.to_s.upcase }
166
+ rows = array.map { |item| item.values.map(&:to_s) }
167
+
168
+ # Calculate column widths
169
+ widths = headers.each_with_index.map do |h, i|
170
+ [h.length, *rows.map { |r| r[i]&.length || 0 }].max
171
+ end
172
+
173
+ lines = []
174
+ # Header
175
+ header_line = headers.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(" ")
176
+ lines << "#{prefix}#{header_line}"
177
+
178
+ # Rows
179
+ rows.each do |row|
180
+ row_line = row.each_with_index.map { |v, i| (v || "-").ljust(widths[i]) }.join(" ")
181
+ lines << "#{prefix}#{row_line}"
182
+ end
183
+
184
+ lines.join("\n")
185
+ end
186
+
187
+ # Formats a value for describe output.
188
+ #
189
+ # @param value [Object] value to format
190
+ # @param key [String] key name (used for status detection)
191
+ # @param pastel [Pastel] pastel instance
192
+ # @return [String] formatted value
193
+ def format_describe_value(value, key, pastel)
194
+ return "-" if value.nil?
195
+
196
+ if key.downcase == "status"
197
+ ColorSupport.colorize_status(value, pastel)
198
+ else
199
+ value.to_s
200
+ end
201
+ end
202
+
203
+ # Converts snake_case or kebab-case key to Title Case.
204
+ # Preserves already-formatted keys (e.g., "CPU", "DNS", "Cloud-Init").
205
+ #
206
+ # @param key [String, Symbol] key to humanize
207
+ # @return [String] humanized key
208
+ def humanize_key(key)
209
+ str = key.to_s
210
+ # If the key is already formatted (contains spaces, is all caps,
211
+ # or has uppercase letters indicating intentional formatting), return as-is
212
+ return str if str.include?(" ") || str == str.upcase || str.match?(/[A-Z]/)
213
+
214
+ str.split(/[_-]/).map(&:capitalize).join(" ")
215
+ end
216
+
217
+ # Renders table using tty-table.
218
+ #
219
+ # Uses :basic renderer for clean kubectl-like output (no borders).
220
+ # Handles non-TTY environments gracefully by rescuing ioctl errors.
221
+ #
222
+ # @param headers [Array<String>] column headers
223
+ # @param rows [Array<Array>] row data
224
+ # @return [String] rendered table
225
+ def render_table(headers, rows)
226
+ table = TTY::Table.new(header: headers, rows: rows)
227
+ table.render(:basic, padding: [0, 2]) || headers.join("\t")
228
+ rescue NoMethodError
229
+ # TTY::Screen may call ioctl on non-TTY streams (e.g., StringIO in tests)
230
+ # Fall back to simple tab-separated output
231
+ ([headers] + rows).map { |row| row.join("\t") }.join("\n")
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-table"
4
+
5
+ module Pvectl
6
+ module Formatters
7
+ # Formats data as a wide table with extended columns.
8
+ #
9
+ # For collections: uses wide_columns and to_wide_row from presenter.
10
+ # For single resources (describe): delegates to Table (no wide variant).
11
+ #
12
+ # @example Wide table output
13
+ # NAME STATUS NODE MEMORY CPU UPTIME
14
+ # vm-100 running pve1 2048 2 3d 5h
15
+ #
16
+ # @see Pvectl::Formatters::Table for standard table output
17
+ # @see Pvectl::Presenters::Base#wide_columns for wide column definitions
18
+ #
19
+ class Wide < Base
20
+ # Formats data as wide table output.
21
+ #
22
+ # @param data [Array, Object] collection of models or single model
23
+ # @param presenter [Presenters::Base] presenter for column/row definitions
24
+ # @param color_enabled [Boolean] whether to apply color formatting
25
+ # @param describe [Boolean] whether this is a describe command
26
+ # @param context [Hash] additional context passed to presenter
27
+ # @return [String] formatted wide table string
28
+ def format(data, presenter, color_enabled: true, describe: false, **context)
29
+ # For describe (single resource), delegate to Table formatter
30
+ # Wide format has no meaning for vertical key-value layout
31
+ if describe || !collection?(data)
32
+ Table.new.format(data, presenter, color_enabled: color_enabled, describe: true, **context)
33
+ else
34
+ format_wide_table(data, presenter, color_enabled, **context)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Formats collection as wide table.
41
+ #
42
+ # @param data [Array] collection of models
43
+ # @param presenter [Presenters::Base] presenter
44
+ # @param color_enabled [Boolean] whether to apply color
45
+ # @param context [Hash] context passed to presenter
46
+ # @return [String] formatted wide table
47
+ def format_wide_table(data, presenter, color_enabled, **context)
48
+ pastel = ColorSupport.pastel(explicit_flag: color_enabled)
49
+ headers = presenter.wide_columns
50
+ rows = data.map do |model|
51
+ row = presenter.to_wide_row(model, **context)
52
+ colorize_row(row, headers, pastel)
53
+ end
54
+
55
+ render_table(headers, rows)
56
+ end
57
+
58
+ # Colorizes status columns in a row.
59
+ #
60
+ # @param row [Array] row values
61
+ # @param headers [Array<String>] column headers
62
+ # @param pastel [Pastel] pastel instance
63
+ # @return [Array] row with colorized values
64
+ def colorize_row(row, headers, pastel)
65
+ row.each_with_index.map do |value, idx|
66
+ header = headers[idx].to_s.downcase
67
+ if header == "status"
68
+ ColorSupport.colorize_status(value, pastel)
69
+ else
70
+ value.nil? ? "-" : value
71
+ end
72
+ end
73
+ end
74
+
75
+ # Renders table using tty-table.
76
+ #
77
+ # Uses :basic renderer for clean kubectl-like output (no borders).
78
+ # Handles non-TTY environments gracefully by rescuing ioctl errors.
79
+ #
80
+ # @param headers [Array<String>] column headers
81
+ # @param rows [Array<Array>] row data
82
+ # @return [String] rendered table
83
+ def render_table(headers, rows)
84
+ table = TTY::Table.new(header: headers, rows: rows)
85
+ table.render(:basic, padding: [0, 2]) || headers.join("\t")
86
+ rescue NoMethodError
87
+ # TTY::Screen may call ioctl on non-TTY streams (e.g., StringIO in tests)
88
+ # Fall back to simple tab-separated output
89
+ ([headers] + rows).map { |row| row.join("\t") }.join("\n")
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Pvectl
6
+ module Formatters
7
+ # Formats data as YAML output.
8
+ #
9
+ # Collections are rendered as YAML arrays.
10
+ # Single resources are rendered as YAML mappings.
11
+ # Empty collections return "--- []\n".
12
+ # Nil values are rendered as YAML null (~).
13
+ #
14
+ # @example YAML output for collection
15
+ # ---
16
+ # - name: vm-100
17
+ # status: running
18
+ # - name: vm-101
19
+ # status: stopped
20
+ #
21
+ # @example YAML output for single resource
22
+ # ---
23
+ # name: vm-100
24
+ # status: running
25
+ # node: pve1
26
+ #
27
+ class Yaml < Base
28
+ # Formats data as YAML output.
29
+ #
30
+ # @param data [Array, Object] collection of models or single model
31
+ # @param presenter [Presenters::Base] presenter for hash conversion
32
+ # @param color_enabled [Boolean] ignored for YAML (always plain text)
33
+ # @param describe [Boolean] whether this is a describe command
34
+ # @param context [Hash] ignored for YAML output
35
+ # @return [String] formatted YAML string
36
+ def format(data, presenter, color_enabled: true, describe: false, **context)
37
+ if describe && !collection?(data)
38
+ # Use to_description for describe mode
39
+ presenter.to_description(data).to_yaml
40
+ elsif collection?(data)
41
+ hashes = data.map { |model| presenter.to_hash(model) }
42
+ hashes.to_yaml
43
+ else
44
+ presenter.to_hash(data).to_yaml
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end