machineconfig 8.14__py3-none-any.whl → 8.50__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (273) hide show
  1. machineconfig/cluster/remote/run_cluster.py +1 -1
  2. machineconfig/cluster/remote/run_remote.py +1 -1
  3. machineconfig/cluster/sessions_managers/utils/maker.py +10 -8
  4. machineconfig/cluster/sessions_managers/wt_local.py +1 -1
  5. machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
  6. machineconfig/cluster/sessions_managers/zellij_local.py +1 -1
  7. machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
  8. machineconfig/jobs/installer/checks/check_installations.py +133 -0
  9. machineconfig/jobs/installer/checks/install_utils.py +132 -0
  10. machineconfig/jobs/installer/checks/report_utils.py +39 -0
  11. machineconfig/jobs/installer/checks/vt_utils.py +89 -0
  12. machineconfig/jobs/installer/installer_data.json +225 -140
  13. machineconfig/jobs/installer/linux_scripts/docker.sh +6 -9
  14. machineconfig/jobs/installer/package_groups.py +10 -9
  15. machineconfig/jobs/installer/python_scripts/boxes.py +1 -2
  16. machineconfig/jobs/installer/python_scripts/code.py +10 -8
  17. machineconfig/jobs/installer/python_scripts/hx.py +30 -13
  18. machineconfig/jobs/installer/python_scripts/nerfont_windows_helper.py +6 -5
  19. machineconfig/jobs/installer/python_scripts/sysabc.py +25 -19
  20. machineconfig/jobs/installer/python_scripts/yazi.py +33 -17
  21. machineconfig/jobs/scripts/powershell_scripts/cmatrix.ps1 +52 -0
  22. machineconfig/jobs/scripts/powershell_scripts/mount_ssh.ps1 +1 -1
  23. machineconfig/jobs/scripts_dynamic/a.py +413 -10
  24. machineconfig/profile/create_links.py +77 -20
  25. machineconfig/profile/create_links_export.py +63 -58
  26. machineconfig/profile/mapper_data.toml +30 -0
  27. machineconfig/profile/mapper_dotfiles.toml +253 -0
  28. machineconfig/scripts/python/agents.py +70 -172
  29. machineconfig/scripts/python/ai/initai.py +3 -1
  30. machineconfig/scripts/python/ai/scripts/__init__.py +1 -0
  31. machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +2 -0
  32. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +7 -5
  33. machineconfig/scripts/python/ai/solutions/claude/claude.py +1 -1
  34. machineconfig/scripts/python/ai/solutions/cline/cline.py +1 -1
  35. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +1 -1
  36. machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +29 -0
  37. machineconfig/scripts/python/ai/solutions/crush/crush.py +1 -1
  38. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +1 -1
  39. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +1 -1
  40. machineconfig/scripts/python/ai/solutions/gemini/settings.json +3 -0
  41. machineconfig/scripts/python/ai/{solutions → utils}/generic.py +2 -15
  42. machineconfig/scripts/python/ai/utils/vscode_tasks.py +6 -3
  43. machineconfig/scripts/python/cloud.py +58 -11
  44. machineconfig/scripts/python/croshell.py +4 -156
  45. machineconfig/scripts/python/devops.py +57 -40
  46. machineconfig/scripts/python/devops_navigator.py +17 -3
  47. machineconfig/scripts/python/fire_jobs.py +8 -207
  48. machineconfig/scripts/python/ftpx.py +5 -225
  49. machineconfig/scripts/python/graph/cli_graph.json +8743 -0
  50. machineconfig/scripts/python/{env_manager → helper_env}/path_manager_tui.py +2 -2
  51. machineconfig/scripts/python/{env_manager → helpers/helper_env}/env_manager_tui.py +1 -1
  52. machineconfig/scripts/python/helpers/helper_env/path_manager_tui.py +228 -0
  53. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.py +1 -1
  54. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
  55. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_gemini.py +1 -1
  56. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_qwen.py +1 -1
  57. machineconfig/scripts/python/helpers/helpers_agents/agents_impl.py +168 -0
  58. machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_launch.py +5 -5
  59. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_copy.py +6 -6
  60. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_mount.py +10 -5
  61. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_sync.py +3 -3
  62. machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers2.py +1 -1
  63. machineconfig/scripts/python/helpers/helpers_croshell/croshell_impl.py +225 -0
  64. machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/scheduler.py +4 -4
  65. machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/start_slidev.py +7 -6
  66. machineconfig/scripts/python/helpers/helpers_devops/backup_config.py +149 -0
  67. machineconfig/scripts/python/helpers/helpers_devops/cli_backup_retrieve.py +267 -0
  68. machineconfig/scripts/python/helpers/helpers_devops/cli_config.py +98 -0
  69. machineconfig/scripts/python/helpers/helpers_devops/cli_config_dotfile.py +274 -0
  70. machineconfig/scripts/python/helpers/helpers_devops/cli_data.py +76 -0
  71. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_nw.py +52 -72
  72. machineconfig/scripts/python/helpers/helpers_devops/cli_repos.py +274 -0
  73. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_self.py +40 -23
  74. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_file.py +44 -30
  75. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_server.py +26 -43
  76. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/cli_share_terminal.py +12 -6
  77. machineconfig/scripts/python/helpers/helpers_devops/cli_ssh.py +167 -0
  78. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_status.py +12 -6
  79. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/devops_update_repos.py +1 -1
  80. machineconfig/scripts/python/{interactive.py → helpers/helpers_devops/interactive.py} +68 -52
  81. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/run_script.py +75 -58
  82. machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.ps1 +41 -0
  83. machineconfig/scripts/python/helpers/helpers_devops/themes/choose_starship_theme.sh +48 -0
  84. machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_wezterm_theme.py +3 -3
  85. machineconfig/scripts/python/helpers/helpers_fire_command/fire_jobs_impl.py +233 -0
  86. machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_route_helper.py +3 -3
  87. machineconfig/scripts/python/helpers/helpers_msearch/msearch_impl.py +248 -0
  88. machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_linux/fzfg +4 -3
  89. machineconfig/scripts/python/helpers/helpers_msearch/scripts_linux/search_with_context.sh +48 -0
  90. machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/scripts_windows/fzfg.ps1 +1 -1
  91. machineconfig/scripts/python/helpers/helpers_navigator/__init__.py +20 -0
  92. machineconfig/scripts/python/helpers/helpers_navigator/cli_graph_loader.py +234 -0
  93. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/command_builder.py +61 -13
  94. machineconfig/scripts/python/helpers/helpers_navigator/command_detail.py +153 -0
  95. machineconfig/scripts/python/helpers/helpers_navigator/command_tree.py +45 -0
  96. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/data_models.py +18 -11
  97. machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/main_app.py +5 -5
  98. machineconfig/scripts/python/helpers/helpers_network/__init__.py +0 -0
  99. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address.py +15 -17
  100. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/address_switch.py +1 -1
  101. machineconfig/scripts/python/helpers/helpers_network/ftpx_impl.py +276 -0
  102. machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_ssh.py +2 -2
  103. machineconfig/scripts/python/helpers/helpers_network/ssh_add_identity.py +73 -0
  104. machineconfig/scripts/python/helpers/helpers_network/ssh_add_ssh_key.py +175 -0
  105. machineconfig/scripts/python/helpers/helpers_network/ssh_debug_linux.py +319 -0
  106. machineconfig/scripts/python/helpers/helpers_network/ssh_debug_windows.py +275 -0
  107. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action.py +3 -3
  108. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/action_helper.py +3 -3
  109. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/cloud_repo_sync.py +117 -33
  110. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/grource.py +3 -2
  111. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/record.py +33 -13
  112. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_2.py +63 -19
  113. machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/update.py +0 -6
  114. machineconfig/scripts/python/helpers/helpers_search/script_help.py +81 -0
  115. machineconfig/scripts/python/helpers/helpers_sessions/__init__.py +0 -0
  116. machineconfig/scripts/python/helpers/helpers_sessions/sessions_impl.py +186 -0
  117. machineconfig/scripts/python/{helpers_sessions → helpers/helpers_sessions}/sessions_multiprocess.py +1 -1
  118. machineconfig/scripts/python/helpers/helpers_terminal/__init__.py +0 -0
  119. machineconfig/scripts/python/helpers/helpers_terminal/terminal_impl.py +96 -0
  120. machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/download.py +1 -1
  121. machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/python.py +47 -26
  122. machineconfig/scripts/python/helpers/helpers_utils/specs.py +246 -0
  123. machineconfig/scripts/python/mcfg_entry.py +133 -48
  124. machineconfig/scripts/python/msearch.py +15 -61
  125. machineconfig/scripts/python/sessions.py +59 -194
  126. machineconfig/scripts/python/terminal.py +18 -96
  127. machineconfig/scripts/python/utils.py +101 -20
  128. machineconfig/settings/atuin/config.toml +294 -0
  129. machineconfig/settings/atuin/themes/catppuccin-mocha-mauve.toml +12 -0
  130. machineconfig/settings/linters/.ruff.toml +1 -0
  131. machineconfig/settings/mprocs/windows/mprocs.yaml +2 -2
  132. machineconfig/settings/shells/bash/init.sh +6 -3
  133. machineconfig/settings/shells/pwsh/init.ps1 +69 -1
  134. machineconfig/settings/shells/pwsh/search_pwsh_history.ps1 +99 -0
  135. machineconfig/settings/shells/wezterm/wezterm.lua +4 -1
  136. machineconfig/settings/shells/wt/settings.json +20 -7
  137. machineconfig/settings/shells/zsh/init.sh +25 -4
  138. machineconfig/settings/television/cable_unix/bash-history.toml +1 -1
  139. machineconfig/settings/television/cable_windows/pwsh-history.toml +1 -1
  140. machineconfig/settings/tv/config.toml +234 -0
  141. machineconfig/settings/tv/themes/catppuccin-mocha-sky.toml +22 -0
  142. machineconfig/settings/wsl/.wslconfig +5 -30
  143. machineconfig/settings/yazi/yazi_linux.toml +18 -8
  144. machineconfig/settings/zellij/layouts/st.kdl +2 -2
  145. machineconfig/settings/zellij/layouts/st2.kdl +1 -1
  146. machineconfig/setup_linux/web_shortcuts/interactive.sh +10 -10
  147. machineconfig/setup_linux/web_shortcuts/live_from_github.sh +3 -0
  148. machineconfig/setup_mac/__init__.py +0 -2
  149. machineconfig/setup_windows/__init__.py +0 -1
  150. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +14 -13
  151. machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +4 -3
  152. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +3 -3
  153. machineconfig/type_hinting/sql/__init__.py +1 -0
  154. machineconfig/type_hinting/sql/base.py +216 -0
  155. machineconfig/type_hinting/sql/core_schema.py +64 -0
  156. machineconfig/type_hinting/sql/core_schema_typeddict.py +41 -0
  157. machineconfig/type_hinting/sql/typeddict_codegen.py +222 -0
  158. machineconfig/type_hinting/typedict/__init__.py +1 -0
  159. machineconfig/type_hinting/typedict/ast_utils.py +130 -0
  160. machineconfig/type_hinting/typedict/generator_helpers.py +319 -0
  161. machineconfig/type_hinting/typedict/generators.py +231 -0
  162. machineconfig/type_hinting/typedict/polars_schema.py +24 -0
  163. machineconfig/type_hinting/typedict/polars_schema_typeddict.py +63 -0
  164. machineconfig/utils/accessories.py +24 -0
  165. machineconfig/utils/code.py +41 -13
  166. machineconfig/utils/files/ascii_art.py +10 -14
  167. machineconfig/utils/files/headers.py +3 -5
  168. machineconfig/utils/files/read.py +8 -1
  169. machineconfig/utils/installer_utils/github_release_bulk.py +11 -91
  170. machineconfig/utils/installer_utils/github_release_scraper.py +99 -0
  171. machineconfig/utils/installer_utils/install_from_url.py +1 -1
  172. machineconfig/utils/installer_utils/installer_class.py +12 -4
  173. machineconfig/utils/installer_utils/installer_cli.py +1 -15
  174. machineconfig/utils/installer_utils/installer_helper.py +2 -2
  175. machineconfig/utils/installer_utils/installer_locator_utils.py +13 -13
  176. machineconfig/utils/installer_utils/installer_runner.py +4 -4
  177. machineconfig/utils/io.py +25 -8
  178. machineconfig/utils/meta.py +6 -4
  179. machineconfig/utils/options.py +49 -19
  180. machineconfig/utils/options_utils/__init__.py +0 -0
  181. machineconfig/utils/options_utils/options_tv_linux.py +211 -0
  182. machineconfig/utils/options_utils/options_tv_windows.py +88 -0
  183. machineconfig/utils/options_utils/tv_options.py +37 -0
  184. machineconfig/utils/path_extended.py +6 -6
  185. machineconfig/utils/scheduler.py +8 -2
  186. machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
  187. machineconfig/utils/source_of_truth.py +6 -1
  188. machineconfig/utils/ssh.py +69 -18
  189. machineconfig/utils/ssh_utils/abc.py +1 -1
  190. machineconfig/utils/ssh_utils/copy_from_here.py +17 -12
  191. machineconfig/utils/ssh_utils/utils.py +21 -5
  192. machineconfig/utils/ssh_utils/wsl.py +107 -170
  193. machineconfig/utils/ssh_utils/wsl_helper.py +217 -0
  194. machineconfig/utils/upgrade_packages.py +4 -8
  195. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/METADATA +29 -22
  196. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/RECORD +251 -211
  197. machineconfig/jobs/installer/check_installations.py +0 -248
  198. machineconfig/profile/backup.toml +0 -49
  199. machineconfig/profile/mapper.toml +0 -263
  200. machineconfig/scripts/python/helpers_devops/cli_config.py +0 -105
  201. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +0 -89
  202. machineconfig/scripts/python/helpers_devops/cli_data.py +0 -25
  203. machineconfig/scripts/python/helpers_devops/cli_repos.py +0 -208
  204. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +0 -80
  205. machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +0 -3
  206. machineconfig/scripts/python/helpers_navigator/__init__.py +0 -20
  207. machineconfig/scripts/python/helpers_navigator/command_detail.py +0 -44
  208. machineconfig/scripts/python/helpers_navigator/command_tree.py +0 -620
  209. machineconfig/scripts/python/helpers_network/ssh_add_identity.py +0 -116
  210. machineconfig/scripts/python/helpers_network/ssh_add_ssh_key.py +0 -153
  211. machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +0 -391
  212. machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +0 -338
  213. machineconfig/scripts/python/helpers_repos/entrypoint.py +0 -77
  214. machineconfig/setup_mac/ssh/openssh_setup.sh +0 -114
  215. machineconfig/setup_windows/ssh/add-sshkey.ps1 +0 -29
  216. machineconfig/setup_windows/ssh/openssh-server.ps1 +0 -37
  217. machineconfig/utils/options_tv.py +0 -119
  218. machineconfig/utils/tst.py +0 -20
  219. /machineconfig/{scripts/python/helpers_agents → jobs/installer/checks}/__init__.py +0 -0
  220. /machineconfig/scripts/python/ai/{solutions/_shared.py → utils/shared.py} +0 -0
  221. /machineconfig/scripts/python/{helpers_agents/agentic_frameworks → graph}/__init__.py +0 -0
  222. /machineconfig/scripts/python/{helpers_cloud → helpers}/__init__.py +0 -0
  223. /machineconfig/scripts/python/{env_manager → helpers/helper_env}/__init__.py +0 -0
  224. /machineconfig/scripts/python/{env_manager → helpers/helper_env}/path_manager_backend.py +0 -0
  225. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_agents}/__init__.py +0 -0
  226. /machineconfig/scripts/python/{helpers_devops → helpers/helpers_agents/agentic_frameworks}/__init__.py +0 -0
  227. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
  228. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_help_search.py +0 -0
  229. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_helper_types.py +0 -0
  230. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/fire_agents_load_balancer.py +0 -0
  231. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aichat/config.yaml +0 -0
  232. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/aider/.aider.conf.yml +0 -0
  233. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/copilot/config.yml +0 -0
  234. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/crush/crush.json +0 -0
  235. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/configs/gemini/settings.json +0 -0
  236. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/privacy/privacy.py +0 -0
  237. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/prompt.txt +0 -0
  238. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.ps1 +0 -0
  239. /machineconfig/scripts/python/{helpers_agents → helpers/helpers_agents}/templates/template.sh +0 -0
  240. /machineconfig/scripts/python/{helpers_devops/themes → helpers/helpers_cloud}/__init__.py +0 -0
  241. /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/cloud_helpers.py +0 -0
  242. /machineconfig/scripts/python/{helpers_cloud → helpers/helpers_cloud}/helpers5.py +0 -0
  243. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_croshell}/__init__.py +0 -0
  244. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/crosh.py +0 -0
  245. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/pomodoro.py +0 -0
  246. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer.py +0 -0
  247. /machineconfig/scripts/python/{helpers_croshell → helpers/helpers_croshell}/viewer_template.py +0 -0
  248. /machineconfig/scripts/python/{helpers_network → helpers/helpers_devops}/__init__.py +0 -0
  249. /machineconfig/scripts/python/{helpers_sessions → helpers/helpers_devops/themes}/__init__.py +0 -0
  250. /machineconfig/scripts/python/{helpers_devops → helpers/helpers_devops}/themes/choose_pwsh_theme.ps1 +0 -0
  251. /machineconfig/scripts/python/{helpers_devops/themes/choose_starship_theme.ps1 → helpers/helpers_fire_command/__init__.py} +0 -0
  252. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/cloud_manager.py +0 -0
  253. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/f.py +0 -0
  254. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/file_wrangler.py +0 -0
  255. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_args_helper.py +0 -0
  256. /machineconfig/scripts/python/{helpers_fire_command → helpers/helpers_fire_command}/fire_jobs_streamlit_helper.py +0 -0
  257. /machineconfig/scripts/python/{helpers_msearch → helpers/helpers_msearch}/__init__.py +0 -0
  258. /machineconfig/scripts/python/{helpers_navigator → helpers/helpers_navigator}/search_bar.py +0 -0
  259. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nfs.py +0 -0
  260. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/mount_nw_drive.py +0 -0
  261. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/onetimeshare.py +0 -0
  262. /machineconfig/scripts/python/{helpers_network → helpers/helpers_network}/wifi_conn.py +0 -0
  263. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/clone.py +0 -0
  264. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/repo_analyzer_1.py +0 -0
  265. /machineconfig/scripts/python/{helpers_repos → helpers/helpers_repos}/sync.py +0 -0
  266. /machineconfig/scripts/python/helpers/{ast_search.py → helpers_search/ast_search.py} +0 -0
  267. /machineconfig/scripts/python/helpers/{qr_code.py → helpers_search/qr_code.py} +0 -0
  268. /machineconfig/scripts/python/helpers/{repo_rag.py → helpers_search/repo_rag.py} +0 -0
  269. /machineconfig/scripts/python/helpers/{symantic_search.py → helpers_search/symantic_search.py} +0 -0
  270. /machineconfig/scripts/python/{helpers_utils → helpers/helpers_utils}/pdf.py +0 -0
  271. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/WHEEL +0 -0
  272. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/entry_points.txt +0 -0
  273. {machineconfig-8.14.dist-info → machineconfig-8.50.dist-info}/top_level.txt +0 -0
@@ -75,18 +75,18 @@ def get_installed_cli_apps():
75
75
  print("🔍 LISTING INSTALLED CLI APPS 🔍")
76
76
  if platform.system() == "Windows":
77
77
  print("🪟 Searching for Windows executables...")
78
- apps = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps").search("*.exe", not_in=["notepad"])
78
+ apps = [p for p in PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps").glob("*.exe") if "notepad" not in str(p)]
79
79
  elif platform.system() in ["Linux", "Darwin"]:
80
80
  print(f"🐧 Searching for {platform.system()} executables...")
81
81
  if platform.system() == "Linux":
82
- apps = PathExtended(LINUX_INSTALL_PATH).search("*") + PathExtended("/usr/local/bin").search("*")
82
+ apps = list(PathExtended(LINUX_INSTALL_PATH).glob("*")) + list(PathExtended("/usr/local/bin").glob("*"))
83
83
  else: # Darwin/macOS
84
- apps = PathExtended("/usr/local/bin").search("*") + PathExtended("/opt/homebrew/bin").search("*")
84
+ apps = list(PathExtended("/usr/local/bin").glob("*")) + list(PathExtended("/opt/homebrew/bin").glob("*"))
85
85
  else:
86
86
  error_msg = f"❌ ERROR: System {platform.system()} not supported"
87
87
  print(error_msg)
88
88
  raise NotImplementedError(error_msg)
89
- apps = [app for app in apps if app.size("kb") > 0.1 and not app.is_symlink()] # no symlinks like paint and wsl and bash
89
+ apps = [app for app in apps if (app.stat().st_size / 1024) > 0.1 and not app.is_symlink()] # no symlinks like paint and wsl and bash
90
90
  print(f"✅ Found {len(apps)} installed applications")
91
91
  return apps
92
92
 
machineconfig/utils/io.py CHANGED
@@ -55,19 +55,36 @@ def read_ini(path: "Path", encoding: Optional[str] = None):
55
55
  return res
56
56
 
57
57
 
58
+ def remove_c_style_comments(text: str) -> str:
59
+ import re
60
+ # Step 1: Escape URLs (https:// or any URLs you want to protect)
61
+ url_pattern = r'https?://[^\s]*'
62
+ urls = re.findall(url_pattern, text)
63
+ url_map = {url: f"__URL{index}__" for index, url in enumerate(urls)}
64
+
65
+ # Temporarily replace URLs with placeholders
66
+ for url, placeholder in url_map.items():
67
+ text = text.replace(url, placeholder)
68
+
69
+ # Step 2: Remove C-style comments
70
+ # Remove all // single-line comments
71
+ text = re.sub(r'//.*', '', text)
72
+ # Remove all /* … */ block comments (non-greedy)
73
+ text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
74
+
75
+ # Step 3: Restore URLs
76
+ for url, placeholder in url_map.items():
77
+ text = text.replace(placeholder, url)
78
+
79
+ return text
80
+
81
+
58
82
  def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
59
83
  import json
60
84
  try:
61
85
  mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
62
86
  except Exception:
63
- import re
64
- def remove_comments(text: str) -> str:
65
- # remove all // single-line comments
66
- text = re.sub(r'//.*', '', text)
67
- # remove all /* … */ block comments (non-greedy)
68
- text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
69
- return text
70
- mydict = json.loads(remove_comments(Path(path).read_text(encoding="utf-8")), **kwargs)
87
+ mydict = json.loads(remove_c_style_comments(Path(path).read_text(encoding="utf-8")), **kwargs)
71
88
  _ = r
72
89
  return mydict
73
90
 
@@ -5,7 +5,7 @@ from typing import Any
5
5
 
6
6
 
7
7
  def get_import_module_string(py_file: str) -> str:
8
- from machineconfig.scripts.python.helpers_fire_command.file_wrangler import get_import_module_code
8
+ from machineconfig.scripts.python.helpers.helpers_fire_command.file_wrangler import get_import_module_code
9
9
  from machineconfig.utils.accessories import get_repo_root
10
10
  from pathlib import Path
11
11
  repo_root = get_repo_root(Path(py_file))
@@ -68,7 +68,7 @@ def lambda_to_python_script(lmb: Callable[[], Any],
68
68
 
69
69
  # sanity checks
70
70
  if not (callable(lmb) and isinstance(lmb, _types.LambdaType)):
71
- raise TypeError("Expected a lambda function object")
71
+ raise TypeError(f"Expected a lambda function object, got {type(lmb)}")
72
72
 
73
73
  src = _inspect.getsource(lmb)
74
74
  src = _textwrap.dedent(src)
@@ -81,7 +81,7 @@ def lambda_to_python_script(lmb: Callable[[], Any],
81
81
  lambda_node = n
82
82
  break
83
83
  if lambda_node is None:
84
- raise ValueError("Could not find a lambda expression in source")
84
+ raise ValueError(f"""Could not find a lambda expression in source, got:\n{lmb}\nwhich is not a lambda, its a {src}""")
85
85
 
86
86
  body = lambda_node.body
87
87
  if not isinstance(body, _ast.Call):
@@ -113,7 +113,7 @@ def lambda_to_python_script(lmb: Callable[[], Any],
113
113
  if not callable(func_obj):
114
114
  raise TypeError("Resolved object is not callable")
115
115
 
116
- func_name = getattr(func_obj, "__name__", "<unknown>")
116
+ func_name = getattr(getattr(func_obj, "__code__", None), "co_name", None) or getattr(func_obj, "__name__", "<unknown>")
117
117
 
118
118
  import_prefix: str = ""
119
119
  if import_module:
@@ -249,8 +249,10 @@ def lambda_to_python_script(lmb: Callable[[], Any],
249
249
  if __name__ == "__main__":
250
250
  from machineconfig.utils.code import print_code
251
251
  import_code_robust = "<import_code_robust>"
252
+ print_code.__name__ = "blah"
252
253
  res = lambda_to_python_script(
253
254
  lambda: print_code(code=import_code_robust, lexer="python", desc="import as module code"),
254
255
  in_global=True, import_module=False
255
256
  )
257
+
256
258
  print(res)
@@ -7,10 +7,10 @@ import subprocess
7
7
  from typing import Optional, Union, Iterable, overload, Literal, cast
8
8
 
9
9
  @overload
10
- def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False) -> T: ...
10
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[False], custom_input: bool = False, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, preview: Optional[Literal["bat"]]=None) -> T: ...
11
11
  @overload
12
- def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, ) -> list[T]: ...
13
- def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, ) -> Union[T, list[T]]:
12
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: Literal[True], custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, preview: Optional[Literal["bat"]]=None) -> list[T]: ...
13
+ def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_input: bool = True, header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, tv: bool = False, preview: Optional[Literal["bat"]]=None, ) -> Union[T, list[T]]:
14
14
  # TODO: replace with https://github.com/tmbo/questionary
15
15
  # # also see https://github.com/charmbracelet/gum
16
16
  options_strings: list[str] = [str(x) for x in options]
@@ -20,11 +20,6 @@ def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_i
20
20
  # from machineconfig.utils.installer_utils.installer_cli import check_tool_exists
21
21
  # print("ch1")
22
22
  if tv and check_tool_exists("tv"):
23
- # from pyfzf.pyfzf import FzfPrompt
24
- # fzf_prompt = FzfPrompt()
25
- # nl = "\n"
26
- # choice_string_multi: list[str] = fzf_prompt.prompt(choices=options_strings, fzf_options=("--multi" if multi else "") + f' --prompt "{prompt.replace(nl, " ")}" --ansi') # --border-label={msg.replace(nl, ' ')}")
27
- # print("ch2")
28
23
  from machineconfig.utils.accessories import randstr
29
24
  options_txt_path = Path.home().joinpath("tmp_results/tmp_files/choices_" + randstr(6) + ".txt")
30
25
  options_txt_path.parent.mkdir(parents=True, exist_ok=True)
@@ -35,34 +30,70 @@ def choose_from_options[T](options: Iterable[T], msg: str, multi: bool, custom_i
35
30
  # the interactive session completes. Do not capture_output or redirect
36
31
  # stdin/stderr here so `tv` stays attached to the terminal.
37
32
  tv_out_path = options_txt_path.with_name(options_txt_path.stem + "_out.txt")
38
- tv_cmd = f"""cat {options_txt_path} | tv --preview-command "bat -n --color=always '{{}}'" --preview-size 30 --ansi true --source-output "{{strip_ansi}}" > {tv_out_path} """
39
- res = subprocess.run(tv_cmd, shell=True)
40
-
33
+ if preview is None:
34
+ preview_line = ""
35
+ elif preview == "bat":
36
+ preview_line = r"""--preview-command "bat -n --color=always {}" --preview-size 70 """
37
+
38
+ import platform
39
+ if platform.system() == "Windows":
40
+ # PowerShell + TUI apps can be finicky when stdin is a pipe. Avoid piping into `tv` and
41
+ # instead provide a `--source-command` so `tv` can keep stdin attached to the console.
42
+ # Also: `tv --ansi` is a flag (no value). Passing `true` makes it a positional argument
43
+ # (channel/path/command), which can lead to confusing behavior.
44
+ source_cmd = f"cmd /C type \"{options_txt_path}\""
45
+ tv_cmd = f"""
46
+ $OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
47
+ tv {preview_line} --ansi --source-command '{source_cmd}' --source-output "{{}}" | Out-File -Encoding utf8 -FilePath "{tv_out_path}" """
48
+ else:
49
+ source_cmd = f'cat "{options_txt_path}"'
50
+ tv_cmd = f"""tv {preview_line} --ansi --source-command "{source_cmd}" --source-output "{{}}" > "{tv_out_path}" """
51
+
52
+ # print(f"Running tv command: {tv_cmd}")
53
+ # print(f"Options file: {options_txt_path}")
54
+ # print(f"Content:\n{options_txt_path.read_text(encoding='utf-8')}")
55
+ # print(f"tv output file: {tv_out_path}")
56
+ from machineconfig.utils.code import run_shell_script
57
+ res = run_shell_script(tv_cmd, display_script=False, clean_env=False)
58
+
41
59
  # If tv returned a non-zero code and there is no output file, treat it as an error.
42
60
  if res.returncode != 0 and not tv_out_path.exists():
43
61
  raise RuntimeError(f"Got error running tv command: {tv_cmd}\nreturncode: {res.returncode}")
44
62
 
45
63
  # Read selections (if any) from the output file created by tv.
46
- out_text = tv_out_path.read_text(encoding="utf-8") if tv_out_path.exists() else ""
64
+ print(f"Reading tv output from: {tv_out_path}")
65
+ out_text = tv_out_path.read_text(encoding="utf-8-sig")
47
66
  choice_string_multi = [x for x in out_text.splitlines() if x.strip() != ""]
48
67
 
68
+ # if len(choice_string_multi) == 0: # e.g. user pressed escape
69
+ # console.print(Panel("❓ No option selected!", title="Error", expand=False))
70
+ # return choose_from_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, tv=tv, multi=multi, custom_input=custom_input)
71
+
49
72
  # Cleanup temporary files
50
73
  options_txt_path.unlink(missing_ok=True)
51
74
  tv_out_path.unlink(missing_ok=True)
52
75
  if not multi:
53
76
  try:
54
77
  choice_one_string = choice_string_multi[0]
55
- if isinstance(list(options)[0], str): return cast(T, choice_one_string)
78
+ if isinstance(list(options)[0], str):
79
+ print(f"✅ Selected option: {choice_one_string}")
80
+ return cast(T, choice_one_string)
56
81
  choice_idx = options_strings.index(choice_one_string)
57
- return list(options)[choice_idx]
82
+ choice_made = list(options)[choice_idx]
83
+ print(f"✅ Selected option: {choice_made}")
84
+ return choice_made
58
85
  except IndexError as ie:
59
- print(f"❌ Error: {options=}, {choice_string_multi=}")
60
- print(f"🔍 Available choices: {choice_string_multi}")
86
+ # print(f"❌ Error: {options=}, {choice_string_multi=}")
87
+ print(f"🔍 Available choices: {len(choice_string_multi)}")
61
88
  raise ie
62
89
  if isinstance(list(options)[0], str):
63
- return cast(list[T], choice_string_multi)
90
+ result2 = cast(list[T], choice_string_multi)
91
+ print(f"✅ Selected options: {result2}")
92
+ return result2
64
93
  choice_idx_s = [options_strings.index(x) for x in choice_string_multi]
65
- return [list(options)[x] for x in choice_idx_s]
94
+ result = [list(options)[x] for x in choice_idx_s]
95
+ print(f"✅ Selected options: {result}")
96
+ return result
66
97
  else:
67
98
  if default is not None:
68
99
  assert default in options, f"Default `{default}` option not in options `{list(options)}`"
@@ -138,7 +169,6 @@ def choose_cloud_interactively() -> str:
138
169
 
139
170
  def get_ssh_hosts() -> list[str]:
140
171
  from paramiko import SSHConfig
141
-
142
172
  c = SSHConfig()
143
173
  c.parse(open(Path.home().joinpath(".ssh/config"), encoding="utf-8"))
144
174
  return list(c.get_hostnames())
File without changes
@@ -0,0 +1,211 @@
1
+
2
+ #!/usr/bin/env python3
3
+ import base64
4
+ import pathlib
5
+ import pprint
6
+ import shutil
7
+ import subprocess
8
+ import tempfile
9
+ import os
10
+ from typing import Any, overload, Literal, Union
11
+
12
+ from git import Optional
13
+
14
+
15
+ def _format_preview_value(value: Any) -> str:
16
+ if isinstance(value, str):
17
+ return value
18
+ return pprint.pformat(value, width=88, sort_dicts=True)
19
+
20
+
21
+ def _toml_inline_table(values: dict[str, str]) -> str:
22
+ if not values:
23
+ return ""
24
+ parts: list[str] = []
25
+ for key in sorted(values.keys()):
26
+ raw_value = values[key]
27
+ escaped = raw_value.replace("\\", "\\\\").replace('"', '\\"')
28
+ parts.append(f'{key} = "{escaped}"')
29
+ return "env = { " + ", ".join(parts) + " }\n"
30
+
31
+
32
+ def _normalize_extension(extension: str | None) -> str | None:
33
+ if extension is None:
34
+ return None
35
+ trimmed = extension.strip()
36
+ if trimmed.startswith("."):
37
+ trimmed = trimmed[1:]
38
+ if not trimmed:
39
+ return None
40
+ return trimmed
41
+
42
+
43
+ def _infer_extension_from_key(key: Optional[str]) -> str | None:
44
+ if not isinstance(key, str):
45
+ return None
46
+ candidate = key.strip()
47
+ if not candidate or any(char.isspace() for char in candidate):
48
+ return None
49
+ suffix = pathlib.Path(candidate).suffix
50
+ if not suffix:
51
+ return None
52
+ return _normalize_extension(suffix)
53
+
54
+
55
+ @overload
56
+ def select_from_options(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: Literal[True], preview_size_percent: float) -> list[str]: ...
57
+ @overload
58
+ def select_from_options(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: Literal[False], preview_size_percent: float) -> Union[str, None]: ...
59
+ def select_from_options(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: bool, preview_size_percent: float) -> Union[Union[str, None], list[str]]:
60
+ keys = list(options_to_preview_mapping.keys())
61
+ if not keys:
62
+ return [] if multi else None
63
+ normalized_extension = _normalize_extension(extension)
64
+ preview_panel_size = max(10, min(90, int(preview_size_percent)))
65
+ terminal_width = shutil.get_terminal_size(fallback=(120, 40)).columns
66
+ preview_width = max(20, int(terminal_width * preview_panel_size / 100) - 4)
67
+ with tempfile.TemporaryDirectory(prefix="tv_channel_") as tmpdir:
68
+ tempdir = pathlib.Path(tmpdir)
69
+ entries: list[str] = []
70
+ index_map: dict[int, str] = {}
71
+ preview_map_path = tempdir / "previews.tsv"
72
+ preview_rows: list[str] = []
73
+ for idx, key in enumerate(keys):
74
+ display_key = key.replace("\t", " ").replace("\n", " ")
75
+ entries.append(f"{idx}\t{display_key}")
76
+ index_map[idx] = key
77
+ preview_value = _format_preview_value(options_to_preview_mapping[key])
78
+ encoded_preview = base64.b64encode(preview_value.encode("utf-8")).decode("ascii")
79
+ entry_extension = normalized_extension or _infer_extension_from_key(key) or ""
80
+ preview_rows.append(f"{idx}\t{encoded_preview}\t{entry_extension}")
81
+ preview_map_path.write_text("\n".join(preview_rows), encoding="utf-8")
82
+ entries_path = tempdir / "entries.tsv"
83
+ entries_path.write_text("\n".join(entries), encoding="utf-8")
84
+ preview_script = tempdir / "preview.sh"
85
+ preview_script.write_text(
86
+ """#!/usr/bin/env bash
87
+ set -euo pipefail
88
+
89
+ idx="$1"
90
+ script_dir="$(cd -- "$(dirname -- "$0")" && pwd)"
91
+ previews_file="${script_dir}/previews.tsv"
92
+
93
+ if [[ ! -f "${previews_file}" ]]; then
94
+ exit 0
95
+ fi
96
+
97
+ encoded_preview="$(awk -F '\t' -v idx="${idx}" '($1==idx){print $2; exit}' "${previews_file}" || true)"
98
+
99
+ if [[ -z "${encoded_preview}" ]]; then
100
+ exit 0
101
+ fi
102
+
103
+ preview_content="$(printf '%s' "${encoded_preview}" | base64 --decode)"
104
+
105
+ preview_ext_from_row="$(awk -F '\t' -v idx="${idx}" '($1==idx){print $3; exit}' "${previews_file}" || true)"
106
+ preview_ext="${MCFG_PREVIEW_EXT:-${preview_ext_from_row}}"
107
+ preview_width="${MCFG_PREVIEW_WIDTH:-}"
108
+ preview_size_pct="${MCFG_PREVIEW_SIZE_PCT:-}"
109
+
110
+ if [[ -z "${preview_width}" && -n "${COLUMNS:-}" ]]; then
111
+ if [[ "${preview_size_pct}" =~ ^[0-9]+$ ]]; then
112
+ preview_width=$((COLUMNS * preview_size_pct / 100))
113
+ else
114
+ preview_width="${COLUMNS}"
115
+ fi
116
+ fi
117
+
118
+ if command -v bat >/dev/null 2>&1; then
119
+ bat_args=(--force-colorization --style=plain --paging=never --wrap=character)
120
+ if [[ -n "${preview_ext}" ]]; then
121
+ bat_args+=(--language "${preview_ext}")
122
+ fi
123
+ if [[ "${preview_width}" =~ ^[0-9]+$ ]]; then
124
+ bat_args+=(--terminal-width "${preview_width}")
125
+ fi
126
+ printf '%s' "${preview_content}" | bat "${bat_args[@]}"
127
+ elif command -v glow >/dev/null 2>&1; then
128
+ printf '%s' "${preview_content}" | glow -
129
+ elif command -v fold >/dev/null 2>&1 && [[ "${preview_width}" =~ ^[0-9]+$ ]]; then
130
+ printf '%s' "${preview_content}" | fold -s -w "${preview_width}"
131
+ else
132
+ printf '%s' "${preview_content}"
133
+ fi
134
+ """,
135
+ encoding="utf-8"
136
+ )
137
+ preview_script.chmod(0o755)
138
+ preview_env: dict[str, str] = {
139
+ "BAT_THEME": "ansi",
140
+ "MCFG_PREVIEW_SIZE_PCT": str(preview_panel_size),
141
+ }
142
+ if normalized_extension is not None:
143
+ preview_env["MCFG_PREVIEW_EXT"] = normalized_extension
144
+ if preview_width > 0:
145
+ preview_env["MCFG_PREVIEW_WIDTH"] = str(preview_width)
146
+ preview_env_line = _toml_inline_table(preview_env)
147
+ channel_config = f"""[metadata]
148
+ name = "temp_options"
149
+ description = "Temporary channel for selecting options"
150
+
151
+ [source]
152
+ command = "bat '{entries_path}'"
153
+ display = "{{split:\\t:1}}"
154
+ output = "{{split:\\t:0}}"
155
+
156
+ [preview]
157
+ command = "{preview_script} {{split:\\t:0}}"
158
+ {preview_env_line}
159
+
160
+ [ui.preview_panel]
161
+ size = {preview_panel_size}
162
+ """
163
+ channel_path = tempdir / "temp_options.toml"
164
+ channel_path.write_text(channel_config, encoding="utf-8")
165
+ env = os.environ.copy()
166
+ tv_config_dir = pathlib.Path.home() / ".config" / "television"
167
+ if not tv_config_dir.exists():
168
+ tv_config_dir = pathlib.Path(os.getenv("XDG_CONFIG_HOME", str(pathlib.Path.home() / ".config"))) / "television"
169
+ cable_dir = tv_config_dir / "cable"
170
+ cable_dir.mkdir(parents=True, exist_ok=True)
171
+ temp_channel_link = cable_dir / "temp_options.toml"
172
+ if temp_channel_link.exists() or temp_channel_link.is_symlink():
173
+ temp_channel_link.unlink()
174
+ temp_channel_link.symlink_to(channel_path)
175
+ output_file = tempdir / "selection.txt"
176
+ try:
177
+ result = subprocess.run(["tv", "temp_options"], check=False, stdout=output_file.open("w"), text=True, env=env)
178
+ finally:
179
+ if temp_channel_link.exists() or temp_channel_link.is_symlink():
180
+ temp_channel_link.unlink()
181
+ if result.returncode not in (0, 130):
182
+ raise SystemExit(result.returncode)
183
+ if result.returncode == 130:
184
+ return [] if multi else None
185
+ if not output_file.exists():
186
+ return [] if multi else None
187
+ selected_lines = [line.strip() for line in output_file.read_text().splitlines() if line.strip()]
188
+ if not selected_lines:
189
+ return [] if multi else None
190
+ selected_keys: list[str] = []
191
+ for line in selected_lines:
192
+ try:
193
+ index = int(line)
194
+ key = index_map.get(index)
195
+ if key is not None:
196
+ selected_keys.append(key)
197
+ except ValueError:
198
+ continue
199
+ if multi:
200
+ return selected_keys
201
+ return selected_keys[0] if selected_keys else None
202
+
203
+
204
+ if __name__ == "__main__":
205
+ demo_mapping: dict[str, str] = {
206
+ "Option 1": "# Option 1\nThis is the preview for option 1.",
207
+ "Option 2": "# Option 2\nThis is the preview for option 2.",
208
+ "Option 3": "# Option 3\nThis is the preview for option 3."
209
+ }
210
+ result = select_from_options(demo_mapping, multi=True, extension="md", preview_size_percent=50)
211
+ print(f"Selected: {result}")
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+ import pathlib
3
+ import pprint
4
+ import shutil
5
+ from typing import Any, Literal, Union, overload
6
+
7
+
8
+ def _format_preview_value(value: Any) -> str:
9
+ if isinstance(value, str):
10
+ return value
11
+ return pprint.pformat(value, width=88, sort_dicts=True)
12
+
13
+
14
+ def _normalize_extension(extension: str | None) -> str | None:
15
+ if extension is None:
16
+ return None
17
+ trimmed = extension.strip()
18
+ if trimmed.startswith("."):
19
+ trimmed = trimmed[1:]
20
+ if not trimmed:
21
+ return None
22
+ return trimmed
23
+
24
+
25
+ @overload
26
+ def select_from_options(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: Literal[True], preview_size_percent: float) -> list[str]: ...
27
+ @overload
28
+ def select_from_options(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: Literal[False], preview_size_percent: float) -> str | None: ...
29
+ def select_from_options(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: bool, preview_size_percent: float) -> Union[list[str], Union[str, None]]:
30
+ keys = list(options_to_preview_mapping.keys())
31
+ if not keys:
32
+ return [] if multi else None
33
+ normalized_extension = _normalize_extension(extension)
34
+ preview_panel_size = max(10, min(90, int(preview_size_percent)))
35
+ from machineconfig.utils.accessories import randstr
36
+ tempdir = pathlib.Path.home() / "tmp_results" / "tmp_files" / f"tv_channel_{randstr(6)}"
37
+ tempdir.mkdir(parents=True, exist_ok=True)
38
+ try:
39
+ index_map: dict[str, str] = {}
40
+ ext_for_preview = normalized_extension or "txt"
41
+ entries_lines: list[str] = []
42
+ for idx, key in enumerate(keys):
43
+ display_key = key.replace("\n", " ")
44
+ entries_lines.append(f"{idx}|{display_key}")
45
+ index_map[str(idx)] = key
46
+ preview_value = _format_preview_value(options_to_preview_mapping[key])
47
+ preview_file = tempdir / f"{idx}.{ext_for_preview}"
48
+ preview_file.write_text(preview_value, encoding="utf-8")
49
+ entries_path = tempdir / "entries.txt"
50
+ entries_path.write_text("\n".join(entries_lines), encoding="utf-8")
51
+ output_file = tempdir / "selection.txt"
52
+ tempdir_fwd = str(tempdir).replace("\\", "/")
53
+ source_cmd = f"cmd /C type \"{entries_path}\""
54
+ preview_cmd = f"bat --force-colorization --style=plain --paging=never {tempdir_fwd}/{{split:|:0}}.{ext_for_preview}"
55
+ tv_cmd = f'''$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
56
+ tv --ansi --source-command '{source_cmd}' --source-display '{{split:|:1}}' --source-output '{{split:|:0}}' --preview-command '{preview_cmd}' --preview-size {preview_panel_size} --no-remote | Out-File -Encoding utf8 -FilePath "{output_file}"
57
+ '''
58
+ from machineconfig.utils.code import run_shell_script
59
+ result = run_shell_script(tv_cmd, display_script=False, clean_env=False)
60
+ if result.returncode not in (0, 130) and not output_file.exists():
61
+ raise SystemExit(result.returncode)
62
+ if result.returncode == 130:
63
+ return [] if multi else None
64
+ if not output_file.exists():
65
+ return [] if multi else None
66
+ selected_lines = [line.strip() for line in output_file.read_text(encoding="utf-8-sig").splitlines() if line.strip()]
67
+ if not selected_lines:
68
+ return [] if multi else None
69
+ selected_keys: list[str] = []
70
+ for line in selected_lines:
71
+ key = index_map.get(line)
72
+ if key is not None:
73
+ selected_keys.append(key)
74
+ if multi:
75
+ return selected_keys
76
+ return selected_keys[0] if selected_keys else None
77
+ finally:
78
+ shutil.rmtree(tempdir, ignore_errors=True)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ demo_mapping: dict[str, str] = {
83
+ "Option 1": "# Option 1\nThis is the preview for option 1.",
84
+ "Option 2": "# Option 2\nThis is the preview for option 2.",
85
+ "Option 3": "# Option 3\nThis is the preview for option 3."
86
+ }
87
+ result = select_from_options(demo_mapping, multi=True, extension="md", preview_size_percent=50)
88
+ print(f"Selected: {result}")
@@ -0,0 +1,37 @@
1
+ from typing import Any, Literal, overload
2
+ import platform
3
+
4
+
5
+ @overload
6
+ def choose_from_dict_with_preview(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: Literal[False], preview_size_percent: float) -> str | None: ...
7
+ @overload
8
+ def choose_from_dict_with_preview(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: Literal[True], preview_size_percent: float) -> list[str]: ...
9
+ def choose_from_dict_with_preview(options_to_preview_mapping: dict[str, Any], extension: str | None, multi: bool, preview_size_percent: float) -> str | list[str] | None:
10
+ if not options_to_preview_mapping:
11
+ return [] if multi else None
12
+ system = platform.system()
13
+ if system == "Windows":
14
+ from machineconfig.utils.options_utils.options_tv_windows import select_from_options
15
+ return select_from_options(options_to_preview_mapping, extension=extension, multi=multi, preview_size_percent=preview_size_percent)
16
+ else:
17
+ from machineconfig.utils.options_utils.options_tv_linux import select_from_options
18
+ return select_from_options(options_to_preview_mapping, extension=extension, multi=multi, preview_size_percent=preview_size_percent)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ demo_mapping: dict[str, str] = {
23
+ "config.py": """from pathlib import Path
24
+
25
+ CONFIG_DIR = Path.home() / ".config"
26
+ DEBUG = True
27
+ """,
28
+ "utils.py": """def greet(name: str) -> str:
29
+ return f"Hello, {name}!"
30
+ """,
31
+ "main.rs": """fn main() {
32
+ println!("Hello, world!");
33
+ }
34
+ """,
35
+ }
36
+ result = choose_from_dict_with_preview(demo_mapping, extension="py", multi=True, preview_size_percent=50)
37
+ print(f"Selected: {result}")
@@ -103,7 +103,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
103
103
  slf = self.expanduser().resolve()
104
104
  if content:
105
105
  assert self.is_dir(), NotADirectoryError(f"💥 When `content` flag is set to True, path must be a directory. It is not: `{repr(self)}`")
106
- [x.move(folder=path.parent, content=False, overwrite=overwrite) for x in self.search("*")]
106
+ [x.move(folder=path.parent, content=False, overwrite=overwrite) for x in self.glob("*")]
107
107
  return path # contents live within this directory.
108
108
  if overwrite:
109
109
  tmp_path = slf.rename(path.parent.absolute() / randstr())
@@ -294,7 +294,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
294
294
  target = "BROKEN LINK " + str(self) # avoid infinite recursions for broken links.
295
295
  return "🔗 Symlink '" + str(self) + "' ==> " + (str(target) if target == self else str(target))
296
296
  elif self.is_absolute():
297
- return self._type() + " '" + str(self.clickable()) + "'" + (" | " + datetime.fromtimestamp(self.stat().st_ctime).isoformat()[:-7].replace("T", " ") if self.exists() else "") + (f" | {self.size()} Mb" if self.is_file() else "")
297
+ return self._type() + " '" + str(self.clickable()) + "'" + (" | " + datetime.fromtimestamp(self.stat().st_ctime).isoformat()[:-7].replace("T", " ") if self.exists() else "") + (f" | {round(self.stat().st_size / (1024**2), 1)} Mb" if self.is_file() else "")
298
298
  elif "http" in str(self):
299
299
  return "🕸️ URL " + str(self.as_url_str())
300
300
  else:
@@ -476,7 +476,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
476
476
  verbose: bool = True,
477
477
  content: bool = False,
478
478
  orig: bool = False,
479
- pwd: Optional[str] = None,
480
479
  mode: FILE_MODE = "w",
481
480
  **kwargs: Any,
482
481
  ) -> "PathExtended":
@@ -493,7 +492,6 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
493
492
  path_resolved = PathExtended(op_zip)
494
493
  else:
495
494
  import shutil
496
-
497
495
  if content:
498
496
  root_dir, base_dir = slf, "."
499
497
  else:
@@ -501,7 +499,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
501
499
  base_name = str(path_resolved)[:-4] if str(path_resolved).endswith(".zip") else str(path_resolved)
502
500
  op_zip = shutil.make_archive(base_name=base_name, format="zip", root_dir=str(root_dir), base_dir=str(base_dir), verbose=False, **kwargs)
503
501
  path_resolved = PathExtended(op_zip)
504
- msg = f"ZIPPED {repr(slf)} ==> {repr(path)}"
502
+ msg = f"ZIPPED {repr(slf)} ==> {repr(path_resolved)}"
505
503
  res_out = PathExtended(path_resolved)
506
504
  ret = self if orig else res_out
507
505
  delayed_msg = ""
@@ -538,7 +536,9 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
538
536
  assert merge is False, "I have not implemented this yet"
539
537
  assert path is None, "I have not implemented this yet"
540
538
  if tmp:
541
- return self.unzip(folder=PathExtended.tmp().joinpath("tmp_unzips").joinpath(randstr()), content=True).joinpath(self.stem)
539
+ tmp_root = PathExtended("~/tmp_results").expanduser()
540
+ tmp_root.mkdir(parents=True, exist_ok=True)
541
+ return self.unzip(folder=tmp_root.joinpath("tmp_unzips").joinpath(randstr()), content=True).joinpath(self.stem)
542
542
  slf = zipfile__ = self.expanduser().resolve()
543
543
  if any(ztype in str(slf.parent) for ztype in (".zip", ".7z")): # path include a zip archive in the middle.
544
544
  tmp__ = [item for item in (".zip", ".7z", "") if item in str(slf)]
@@ -166,7 +166,10 @@ class CacheMemory[T]():
166
166
  else:
167
167
  age = datetime.now() - self.time_produced
168
168
  if (age > self.expire) or (fresh and (age.total_seconds() > tolerance_seconds)):
169
- self.logger.warning(f"""🔄 CACHE UPDATE ⚠️ {self.name} cache: Updating cache from source func. """ + f""" ⏱️ Age = {age} > {self.expire}""" if not fresh else f""" ⏱️ Age = {age}. Fresh flag raised.""")
169
+ if not fresh:
170
+ self.logger.warning(f"""🔄 CACHE STALE 📦 {self.name} cache: Populating fresh cache from source func. """ + f"""⏱️ Age = {age} > Expiry {self.expire} """ )
171
+ else:
172
+ self.logger.warning(f"""⚠️ Fresh flag raised, age = {age} > {tolerance_seconds} seconds of tolerance. Updating {self.name} cache from source func.""")
170
173
  t0 = time.time()
171
174
  self.cache = self.source_func()
172
175
  self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")
@@ -202,7 +205,10 @@ class Cache[T](): # This class helps to accelrate access to latest data coming
202
205
  if self.path.exists(): # prefer to read from disk over source func as a default source of cache.
203
206
  age = datetime.now() - datetime.fromtimestamp(self.path.stat().st_mtime)
204
207
  if (age > self.expire) or (fresh and (age.total_seconds() > tolerance_seconds)): # cache is old or if fresh flag is raised
205
- self.logger.warning(f"""🔄 CACHE STALE 📦 {self.name} cache: Populating fresh cache from source func. """ + f"""⏱️ Age = {age} > Expiry {self.expire} """ if not fresh else """⚠️ Fresh flag raised.""")
208
+ if not fresh:
209
+ self.logger.warning(f"""🔄 CACHE STALE 📦 {self.name} cache: Populating fresh cache from source func. """ + f"""⏱️ Age = {age} > Expiry {self.expire} """ )
210
+ else:
211
+ self.logger.warning(f"""⚠️ Fresh flag raised, age = {age} > {tolerance_seconds} seconds of tolerance. Updating {self.name} cache from source func.""")
206
212
  t0 = time.time()
207
213
  self.cache = self.source_func() # fresh data.
208
214
  self.logger.warning(f"⏱️ Cache population took {time.time() - t0:.2f} seconds.")