machineconfig 5.15__py3-none-any.whl → 7.98__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.
- machineconfig/__init__.py +0 -28
- machineconfig/cluster/remote/cloud_manager.py +1 -1
- machineconfig/cluster/remote/distribute.py +0 -1
- machineconfig/cluster/remote/file_manager.py +0 -2
- machineconfig/cluster/remote/script_execution.py +0 -1
- machineconfig/cluster/sessions_managers/{utils → helpers}/enhanced_command_runner.py +4 -6
- machineconfig/cluster/sessions_managers/utils/load_balancer.py +1 -1
- machineconfig/cluster/sessions_managers/utils/maker.py +69 -0
- machineconfig/cluster/sessions_managers/wt_local.py +114 -289
- machineconfig/cluster/sessions_managers/wt_local_manager.py +70 -210
- machineconfig/cluster/sessions_managers/wt_remote.py +51 -43
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +52 -198
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +6 -19
- machineconfig/cluster/sessions_managers/wt_utils/manager_persistence.py +52 -0
- machineconfig/cluster/sessions_managers/wt_utils/monitoring_helpers.py +50 -0
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +4 -2
- machineconfig/cluster/sessions_managers/wt_utils/status_reporting.py +76 -0
- machineconfig/cluster/sessions_managers/wt_utils/wt_helpers.py +199 -0
- machineconfig/cluster/sessions_managers/zellij_local.py +81 -375
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +25 -170
- machineconfig/cluster/sessions_managers/zellij_remote.py +40 -41
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +16 -12
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -8
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +5 -20
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +3 -9
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -1
- machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_helper.py +298 -0
- machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_helper_restart.py +77 -0
- machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_helper_with_panes.py +228 -0
- machineconfig/cluster/sessions_managers/zellij_utils/zellij_local_manager_helper.py +165 -0
- machineconfig/jobs/{python → installer}/check_installations.py +2 -3
- machineconfig/jobs/installer/custom/boxes.py +61 -0
- machineconfig/jobs/installer/custom/hx.py +76 -19
- machineconfig/jobs/installer/custom/yazi.py +119 -0
- machineconfig/jobs/installer/custom_dev/alacritty.py +4 -4
- machineconfig/jobs/installer/custom_dev/brave.py +5 -9
- machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
- machineconfig/jobs/installer/custom_dev/code.py +4 -1
- machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +30 -0
- machineconfig/jobs/installer/custom_dev/nerdfont.py +1 -1
- machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +33 -28
- machineconfig/jobs/installer/custom_dev/sysabc.py +139 -0
- machineconfig/jobs/installer/custom_dev/wezterm.py +2 -19
- machineconfig/jobs/installer/custom_dev/winget.py +10 -14
- machineconfig/jobs/installer/installer_data.json +1487 -229
- machineconfig/jobs/installer/linux_scripts/brave.sh +4 -14
- machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +5 -17
- machineconfig/jobs/installer/linux_scripts/docker.sh +5 -17
- machineconfig/jobs/installer/linux_scripts/docker_start.sh +6 -14
- machineconfig/jobs/installer/linux_scripts/edge.sh +3 -11
- machineconfig/jobs/{linux/msc → installer/linux_scripts}/lid.sh +2 -8
- machineconfig/jobs/installer/linux_scripts/nerdfont.sh +5 -17
- machineconfig/jobs/{linux/msc → installer/linux_scripts}/network.sh +2 -8
- machineconfig/jobs/installer/linux_scripts/q.sh +10 -6
- machineconfig/jobs/installer/linux_scripts/redis.sh +6 -17
- machineconfig/jobs/installer/linux_scripts/vscode.sh +5 -17
- machineconfig/jobs/installer/linux_scripts/wezterm.sh +4 -12
- machineconfig/jobs/installer/package_groups.py +106 -177
- machineconfig/jobs/installer/powershell_scripts/install_fonts.ps1 +129 -34
- machineconfig/logger.py +0 -1
- machineconfig/profile/backup.toml +49 -0
- machineconfig/profile/bash_shell_profiles.md +11 -0
- machineconfig/profile/create_helper.py +62 -0
- machineconfig/profile/create_links.py +288 -0
- machineconfig/profile/create_links_export.py +100 -0
- machineconfig/profile/create_shell_profile.py +147 -0
- machineconfig/profile/mapper.toml +263 -0
- machineconfig/scripts/__init__.py +0 -4
- machineconfig/scripts/linux/{share_cloud.sh → other/share_cloud.sh} +14 -25
- machineconfig/scripts/linux/wrap_mcfg +46 -0
- machineconfig/scripts/nu/wrap_mcfg.nu +69 -0
- machineconfig/scripts/python/agents.py +123 -117
- machineconfig/scripts/python/ai/initai.py +3 -28
- machineconfig/scripts/python/ai/scripts/command_runner.ps1 +33 -0
- machineconfig/scripts/python/ai/scripts/command_runner.sh +9 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +17 -18
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +17 -18
- machineconfig/scripts/python/ai/solutions/_shared.py +9 -1
- machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Thinking-Beast-Mode.chatmode.md → agents/Thinking-Beast-Mode.agent.md} +0 -1
- machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md → agents/Ultimate-Transparent-Thinking-Beast-Mode.agent.md} +0 -1
- machineconfig/scripts/python/ai/solutions/copilot/{chatmodes/deepResearch.chatmode.md → agents/deepResearch.agent.md} +2 -2
- machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +5 -5
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +5 -1
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/watch_exec.prompt.md +20 -0
- machineconfig/scripts/python/ai/solutions/copilot/prompts/pyright_fix.md +16 -0
- machineconfig/scripts/python/ai/solutions/generic.py +28 -5
- machineconfig/scripts/python/ai/utils/generate_files.py +348 -0
- machineconfig/scripts/python/ai/utils/vscode_tasks.py +37 -0
- machineconfig/scripts/python/cloud.py +29 -0
- machineconfig/scripts/python/croshell.py +137 -113
- machineconfig/scripts/python/devops.py +61 -101
- machineconfig/scripts/python/devops_navigator.py +6 -0
- machineconfig/scripts/python/env_manager/__init__.py +1 -0
- machineconfig/scripts/python/env_manager/env_manager_tui.py +204 -0
- machineconfig/scripts/python/env_manager/path_manager_backend.py +47 -0
- machineconfig/scripts/python/env_manager/path_manager_tui.py +228 -0
- machineconfig/scripts/python/fire_jobs.py +110 -150
- machineconfig/scripts/python/ftpx.py +51 -24
- machineconfig/scripts/python/helpers/ast_search.py +74 -0
- machineconfig/scripts/python/helpers/qr_code.py +166 -0
- machineconfig/scripts/python/helpers/repo_rag.py +325 -0
- machineconfig/scripts/python/helpers/run_py_script.py +79 -0
- machineconfig/scripts/python/helpers/symantic_search.py +25 -0
- machineconfig/scripts/python/helpers/tmp_py_scripts/a.py +26 -0
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.json +14 -0
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_crush.py +39 -0
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_cursor_agents.py +22 -0
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_gemini.py +55 -0
- machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +30 -0
- machineconfig/scripts/python/helpers_agents/fire_agents_help_launch.py +126 -0
- machineconfig/scripts/python/helpers_agents/fire_agents_helper_types.py +41 -0
- machineconfig/scripts/python/helpers_agents/templates/prompt.txt +10 -0
- machineconfig/scripts/python/helpers_agents/templates/template.ps1 +14 -0
- machineconfig/scripts/python/helpers_agents/templates/template.sh +32 -0
- machineconfig/scripts/python/{cloud_copy.py → helpers_cloud/cloud_copy.py} +30 -23
- machineconfig/scripts/python/{cloud_mount.py → helpers_cloud/cloud_mount.py} +29 -35
- machineconfig/scripts/python/{cloud_sync.py → helpers_cloud/cloud_sync.py} +12 -18
- machineconfig/scripts/python/{helpers → helpers_cloud}/helpers2.py +1 -1
- machineconfig/scripts/python/helpers_croshell/crosh.py +39 -0
- machineconfig/scripts/python/{start_slidev.py → helpers_croshell/start_slidev.py} +8 -9
- machineconfig/scripts/python/helpers_devops/cli_config.py +105 -0
- machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +89 -0
- machineconfig/scripts/python/helpers_devops/cli_data.py +25 -0
- machineconfig/scripts/python/helpers_devops/cli_nw.py +214 -0
- machineconfig/scripts/python/helpers_devops/cli_repos.py +215 -0
- machineconfig/scripts/python/helpers_devops/cli_self.py +172 -0
- machineconfig/scripts/python/helpers_devops/cli_share_file.py +137 -0
- machineconfig/scripts/python/helpers_devops/cli_share_server.py +142 -0
- machineconfig/scripts/python/{share_terminal.py → helpers_devops/cli_share_terminal.py} +45 -35
- machineconfig/scripts/python/helpers_devops/cli_utils.py +96 -0
- machineconfig/scripts/python/{devops_backup_retrieve.py → helpers_devops/devops_backup_retrieve.py} +7 -10
- machineconfig/scripts/python/helpers_devops/devops_status.py +499 -0
- machineconfig/scripts/python/{devops_update_repos.py → helpers_devops/devops_update_repos.py} +68 -49
- machineconfig/scripts/python/helpers_devops/themes/choose_pwsh_theme.ps1 +81 -0
- machineconfig/scripts/python/helpers_devops/themes/choose_starship_theme.bash +3 -0
- machineconfig/scripts/python/{choose_wezterm_theme.py → helpers_devops/themes/choose_wezterm_theme.py} +3 -3
- machineconfig/scripts/python/helpers_fire_command/__init__.py +0 -0
- machineconfig/scripts/python/helpers_fire_command/f.py +0 -0
- machineconfig/scripts/python/{helpers/helpers4.py → helpers_fire_command/file_wrangler.py} +56 -20
- machineconfig/scripts/python/{fire_jobs_args_helper.py → helpers_fire_command/fire_jobs_args_helper.py} +5 -1
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +121 -0
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_streamlit_helper.py +0 -0
- machineconfig/scripts/python/helpers_msearch/__init__.py +5 -0
- machineconfig/scripts/{linux → python/helpers_msearch/scripts_linux}/fzfg +3 -3
- machineconfig/scripts/python/helpers_msearch/scripts_windows/fzfg.ps1 +59 -0
- machineconfig/scripts/python/helpers_navigator/__init__.py +20 -0
- machineconfig/scripts/python/helpers_navigator/command_builder.py +111 -0
- machineconfig/scripts/python/helpers_navigator/command_detail.py +44 -0
- machineconfig/scripts/python/helpers_navigator/command_tree.py +620 -0
- machineconfig/scripts/python/helpers_navigator/data_models.py +28 -0
- machineconfig/scripts/python/helpers_navigator/main_app.py +272 -0
- machineconfig/scripts/python/helpers_navigator/search_bar.py +15 -0
- machineconfig/scripts/python/helpers_network/__init__.py +0 -0
- machineconfig/scripts/python/helpers_network/address.py +132 -0
- machineconfig/scripts/python/{devops_add_identity.py → helpers_network/devops_add_identity.py} +0 -2
- machineconfig/scripts/python/helpers_network/devops_add_ssh_key.py +153 -0
- machineconfig/scripts/{linux → python/helpers_network}/mount_nfs +0 -1
- machineconfig/scripts/python/{mount_nfs.py → helpers_network/mount_nfs.py} +3 -3
- machineconfig/scripts/{linux → python/helpers_network}/mount_nw_drive +1 -2
- machineconfig/scripts/python/{mount_ssh.py → helpers_network/mount_ssh.py} +3 -3
- machineconfig/scripts/python/{onetimeshare.py → helpers_network/onetimeshare.py} +0 -1
- machineconfig/scripts/python/helpers_network/ssh_debug_linux.py +391 -0
- machineconfig/scripts/python/helpers_network/ssh_debug_windows.py +338 -0
- machineconfig/scripts/python/{wifi_conn.py → helpers_network/wifi_conn.py} +1 -53
- machineconfig/scripts/python/{wsl_windows_transfer.py → helpers_network/wsl_windows_transfer.py} +5 -4
- machineconfig/scripts/python/helpers_repos/action.py +209 -0
- machineconfig/scripts/python/helpers_repos/action_helper.py +150 -0
- machineconfig/scripts/python/{repos_helper_clone.py → helpers_repos/clone.py} +2 -3
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +218 -0
- machineconfig/scripts/python/{repos_helper.py → helpers_repos/entrypoint.py} +9 -17
- machineconfig/scripts/python/helpers_repos/grource.py +340 -0
- machineconfig/scripts/python/{repos_helper_record.py → helpers_repos/record.py} +4 -3
- machineconfig/scripts/python/helpers_repos/repo_analyzer_1.py +160 -0
- machineconfig/scripts/python/{count_lines.py → helpers_repos/repo_analyzer_2.py} +113 -192
- machineconfig/scripts/python/helpers_repos/sync.py +66 -0
- machineconfig/scripts/python/{repos_helper_update.py → helpers_repos/update.py} +3 -3
- machineconfig/scripts/python/helpers_sessions/__init__.py +0 -0
- machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +65 -0
- machineconfig/scripts/python/helpers_utils/download.py +150 -0
- machineconfig/scripts/python/helpers_utils/path.py +185 -0
- machineconfig/scripts/python/interactive.py +64 -84
- machineconfig/scripts/python/mcfg_entry.py +58 -0
- machineconfig/scripts/python/msearch.py +71 -0
- machineconfig/scripts/python/sessions.py +119 -45
- machineconfig/scripts/python/terminal.py +133 -0
- machineconfig/scripts/python/utils.py +64 -0
- machineconfig/scripts/windows/mounts/Restore-ThunderbirdProfile.ps1 +92 -0
- machineconfig/scripts/windows/{mount_nfs.ps1 → mounts/mount_nfs.ps1} +1 -3
- machineconfig/scripts/windows/{mount_ssh.ps1 → mounts/mount_ssh.ps1} +1 -1
- machineconfig/scripts/windows/{share_smb.ps1 → mounts/share_smb.ps1} +0 -6
- machineconfig/scripts/windows/wrap_mcfg.ps1 +63 -0
- machineconfig/settings/broot/br.sh +0 -4
- machineconfig/settings/broot/conf.toml +1 -1
- machineconfig/settings/helix/config.toml +16 -0
- machineconfig/settings/helix/languages.toml +13 -4
- machineconfig/settings/helix/yazi-picker.sh +12 -0
- machineconfig/settings/lf/linux/exe/lfcd.sh +1 -0
- machineconfig/settings/lf/linux/exe/previewer.sh +9 -3
- machineconfig/settings/lf/linux/lfrc +10 -12
- machineconfig/settings/lf/windows/lfcd.ps1 +1 -1
- machineconfig/settings/lf/windows/lfrc +18 -38
- machineconfig/settings/lf/windows/mkfile.ps1 +1 -1
- machineconfig/settings/linters/.ruff.toml +1 -1
- machineconfig/settings/lvim/windows/archive/config_additional.lua +0 -6
- machineconfig/settings/marimo/marimo.toml +80 -0
- machineconfig/settings/marimo/snippets/globalize.py +34 -0
- machineconfig/settings/pistol/pistol.conf +1 -1
- machineconfig/settings/shells/bash/init.sh +82 -31
- machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +1 -1
- machineconfig/settings/shells/nushell/config.nu +2 -35
- machineconfig/settings/shells/nushell/env.nu +45 -6
- machineconfig/settings/shells/nushell/init.nu +314 -0
- machineconfig/settings/shells/pwsh/init.ps1 +61 -43
- machineconfig/settings/shells/starship/starship.toml +16 -0
- machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
- machineconfig/settings/shells/wt/settings.json +32 -17
- machineconfig/settings/shells/zsh/init.sh +89 -0
- machineconfig/settings/svim/linux/init.toml +0 -4
- machineconfig/settings/svim/windows/init.toml +0 -3
- machineconfig/settings/television/cable_unix/alias.toml +8 -0
- machineconfig/settings/television/cable_unix/aws-buckets.toml +14 -0
- machineconfig/settings/television/cable_unix/aws-instances.toml +13 -0
- machineconfig/settings/television/cable_unix/bash-history.toml +8 -0
- machineconfig/settings/television/cable_unix/channels.toml +19 -0
- machineconfig/settings/television/cable_unix/dirs.toml +13 -0
- machineconfig/settings/television/cable_unix/distrobox-list.toml +42 -0
- machineconfig/settings/television/cable_unix/docker-images.toml +13 -0
- machineconfig/settings/television/cable_unix/dotfiles.toml +11 -0
- machineconfig/settings/television/cable_unix/env.toml +17 -0
- machineconfig/settings/television/cable_unix/files.toml +11 -0
- machineconfig/settings/television/cable_unix/fish-history.toml +8 -0
- machineconfig/settings/television/cable_unix/git-branch.toml +11 -0
- machineconfig/settings/television/cable_unix/git-diff.toml +10 -0
- machineconfig/settings/television/cable_unix/git-log.toml +12 -0
- machineconfig/settings/television/cable_unix/git-reflog.toml +12 -0
- machineconfig/settings/television/cable_unix/git-repos.toml +16 -0
- machineconfig/settings/television/cable_unix/guix.toml +20 -0
- machineconfig/settings/television/cable_unix/just-recipes.toml +18 -0
- machineconfig/settings/television/cable_unix/k8s-deployments.toml +36 -0
- machineconfig/settings/television/cable_unix/k8s-pods.toml +50 -0
- machineconfig/settings/television/cable_unix/k8s-services.toml +36 -0
- machineconfig/settings/television/cable_unix/man-pages.toml +24 -0
- machineconfig/settings/television/cable_unix/nu-history.toml +7 -0
- machineconfig/settings/television/cable_unix/procs.toml +20 -0
- machineconfig/settings/television/cable_unix/text.toml +17 -0
- machineconfig/settings/television/cable_unix/tldr.toml +18 -0
- machineconfig/settings/television/cable_unix/zsh-history.toml +9 -0
- machineconfig/settings/television/cable_windows/alias.toml +7 -0
- machineconfig/settings/television/cable_windows/dirs.toml +13 -0
- machineconfig/settings/television/cable_windows/docker-images.toml +13 -0
- machineconfig/settings/television/cable_windows/dotfiles.toml +11 -0
- machineconfig/settings/television/cable_windows/env.toml +17 -0
- machineconfig/settings/television/cable_windows/files.toml +14 -0
- machineconfig/settings/television/cable_windows/git-branch.toml +11 -0
- machineconfig/settings/television/cable_windows/git-diff.toml +10 -0
- machineconfig/settings/television/cable_windows/git-log.toml +11 -0
- machineconfig/settings/television/cable_windows/git-reflog.toml +11 -0
- machineconfig/settings/television/cable_windows/git-repos.toml +15 -0
- machineconfig/settings/television/cable_windows/nu-history.toml +7 -0
- machineconfig/settings/television/cable_windows/pwsh-history.toml +6 -0
- machineconfig/settings/television/cable_windows/text.toml +17 -0
- machineconfig/settings/yazi/init.lua +61 -0
- machineconfig/settings/yazi/keymap_linux.toml +94 -0
- machineconfig/settings/yazi/keymap_windows.toml +78 -0
- machineconfig/settings/yazi/shell/yazi_cd.ps1 +33 -0
- machineconfig/settings/yazi/shell/yazi_cd.sh +8 -0
- machineconfig/settings/yazi/theme.toml +4 -0
- machineconfig/settings/yazi/yazi_linux.toml +84 -0
- machineconfig/settings/yazi/yazi_windows.toml +58 -0
- machineconfig/setup_linux/__init__.py +11 -0
- machineconfig/setup_linux/apps_desktop.sh +89 -0
- machineconfig/setup_linux/apps_gui.sh +64 -0
- machineconfig/setup_linux/ssh/openssh_all.sh +25 -0
- machineconfig/setup_linux/ssh/openssh_wsl.sh +38 -0
- machineconfig/setup_linux/uv.sh +15 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +26 -6
- machineconfig/setup_linux/web_shortcuts/live_from_github.sh +31 -0
- machineconfig/setup_mac/__init__.py +16 -0
- machineconfig/setup_mac/apps_gui.sh +248 -0
- machineconfig/setup_mac/ssh/openssh_setup.sh +114 -0
- machineconfig/setup_mac/uv.sh +36 -0
- machineconfig/setup_windows/__init__.py +11 -0
- machineconfig/setup_windows/others/power_options.ps1 +7 -0
- machineconfig/setup_windows/ssh/add-sshkey.ps1 +29 -0
- machineconfig/setup_windows/ssh/add_identity.ps1 +11 -0
- machineconfig/setup_windows/ssh/openssh-server.ps1 +37 -0
- machineconfig/setup_windows/uv.ps1 +17 -0
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +27 -10
- machineconfig/setup_windows/web_shortcuts/live_from_github.ps1 +30 -0
- machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +17 -0
- machineconfig/utils/accessories.py +7 -5
- machineconfig/utils/cloud/onedrive/README.md +139 -0
- machineconfig/utils/code.py +155 -105
- machineconfig/utils/files/art/fat_croco.txt +10 -0
- machineconfig/utils/files/art/halfwit_croco.txt +9 -0
- machineconfig/utils/files/art/happy_croco.txt +22 -0
- machineconfig/utils/files/art/water_croco.txt +11 -0
- machineconfig/utils/files/ascii_art.py +1 -1
- machineconfig/utils/files/dbms.py +257 -0
- machineconfig/utils/files/headers.py +11 -14
- machineconfig/utils/files/ouch/__init__.py +0 -0
- machineconfig/utils/files/ouch/decompress.py +45 -0
- machineconfig/utils/files/read.py +10 -18
- machineconfig/utils/installer_utils/github_release_bulk.py +156 -119
- machineconfig/utils/installer_utils/install_from_url.py +183 -0
- machineconfig/utils/installer_utils/installer_class.py +64 -181
- machineconfig/utils/installer_utils/installer_cli.py +175 -0
- machineconfig/utils/installer_utils/installer_helper.py +129 -0
- machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +66 -97
- machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +49 -82
- machineconfig/utils/io.py +77 -23
- machineconfig/utils/links.py +254 -162
- machineconfig/utils/meta.py +256 -0
- machineconfig/utils/notifications.py +1 -1
- machineconfig/utils/options.py +46 -18
- machineconfig/utils/options_tv.py +119 -0
- machineconfig/utils/path_extended.py +48 -101
- machineconfig/utils/path_helper.py +76 -23
- machineconfig/utils/procs.py +50 -70
- machineconfig/utils/scheduler.py +88 -124
- machineconfig/utils/scheduling.py +0 -3
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/schemas/layouts/layout_types.py +1 -1
- machineconfig/utils/source_of_truth.py +3 -6
- machineconfig/utils/ssh.py +263 -274
- machineconfig/utils/ssh_utils/abc.py +5 -0
- machineconfig/utils/ssh_utils/copy_from_here.py +111 -0
- machineconfig/utils/ssh_utils/copy_to_here.py +302 -0
- machineconfig/utils/ssh_utils/utils.py +142 -0
- machineconfig/utils/ssh_utils/wsl.py +210 -0
- machineconfig/utils/terminal.py +3 -113
- machineconfig/utils/tst.py +20 -0
- machineconfig/utils/upgrade_packages.py +114 -28
- machineconfig/utils/ve.py +12 -4
- machineconfig-7.98.dist-info/METADATA +132 -0
- machineconfig-7.98.dist-info/RECORD +504 -0
- machineconfig-7.98.dist-info/entry_points.txt +13 -0
- machineconfig/cluster/sessions_managers/ffile.py +0 -4
- machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -49
- machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -85
- machineconfig/jobs/linux/msc/cli_agents.sh +0 -16
- machineconfig/jobs/python/python_ve_symlink.py +0 -37
- machineconfig/jobs/python/vscode/api.py +0 -57
- machineconfig/jobs/python/vscode/sync_code.py +0 -73
- machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +0 -14
- machineconfig/jobs/windows/start_terminal.ps1 +0 -6
- machineconfig/jobs/windows/startup_file.cmd +0 -2
- machineconfig/profile/create.py +0 -303
- machineconfig/profile/shell.py +0 -176
- machineconfig/scripts/cloud/init.sh +0 -119
- machineconfig/scripts/linux/agents +0 -2
- machineconfig/scripts/linux/choose_wezterm_theme +0 -3
- machineconfig/scripts/linux/cloud_copy +0 -2
- machineconfig/scripts/linux/cloud_mount +0 -2
- machineconfig/scripts/linux/cloud_repo_sync +0 -2
- machineconfig/scripts/linux/cloud_sync +0 -2
- machineconfig/scripts/linux/croshell +0 -3
- machineconfig/scripts/linux/devops +0 -2
- machineconfig/scripts/linux/fire +0 -2
- machineconfig/scripts/linux/ftpx +0 -2
- machineconfig/scripts/linux/fzf2g +0 -21
- machineconfig/scripts/linux/fzfag +0 -17
- machineconfig/scripts/linux/fzffg +0 -25
- machineconfig/scripts/linux/fzfrga +0 -21
- machineconfig/scripts/linux/gh_models +0 -2
- machineconfig/scripts/linux/initai +0 -2
- machineconfig/scripts/linux/kill_process +0 -2
- machineconfig/scripts/linux/scheduler +0 -2
- machineconfig/scripts/linux/sessions +0 -2
- machineconfig/scripts/linux/share_smb +0 -1
- machineconfig/scripts/linux/skrg +0 -4
- machineconfig/scripts/linux/start_slidev +0 -2
- machineconfig/scripts/linux/start_terminals +0 -3
- machineconfig/scripts/linux/warp-cli.sh +0 -122
- machineconfig/scripts/linux/wifi_conn +0 -2
- machineconfig/scripts/linux/z_ls +0 -104
- machineconfig/scripts/python/ai/generate_files.py +0 -83
- machineconfig/scripts/python/ai/solutions/copilot/prompts/allLintersAndTypeCheckers.prompt.md +0 -5
- machineconfig/scripts/python/cloud_repo_sync.py +0 -190
- machineconfig/scripts/python/count_lines_frontend.py +0 -16
- machineconfig/scripts/python/devops_add_ssh_key.py +0 -120
- machineconfig/scripts/python/dotfile.py +0 -78
- machineconfig/scripts/python/fire_agents_help_launch.py +0 -120
- machineconfig/scripts/python/fire_agents_helper_types.py +0 -12
- machineconfig/scripts/python/fire_jobs_route_helper.py +0 -65
- machineconfig/scripts/python/get_zellij_cmd.py +0 -15
- machineconfig/scripts/python/gh_models.py +0 -104
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +0 -116
- machineconfig/scripts/python/repos.py +0 -132
- machineconfig/scripts/python/repos_helper_action.py +0 -378
- machineconfig/scripts/python/snapshot.py +0 -25
- machineconfig/scripts/python/start_terminals.py +0 -121
- machineconfig/scripts/python/t4.py +0 -17
- machineconfig/scripts/windows/agents.ps1 +0 -1
- machineconfig/scripts/windows/choose_wezterm_theme.ps1 +0 -1
- machineconfig/scripts/windows/cloud_copy.ps1 +0 -1
- machineconfig/scripts/windows/cloud_mount.ps1 +0 -1
- machineconfig/scripts/windows/cloud_repo_sync.ps1 +0 -1
- machineconfig/scripts/windows/cloud_sync.ps1 +0 -1
- machineconfig/scripts/windows/croshell.ps1 +0 -1
- machineconfig/scripts/windows/devops.ps1 +0 -1
- machineconfig/scripts/windows/dotfile.ps1 +0 -1
- machineconfig/scripts/windows/fire.ps1 +0 -1
- machineconfig/scripts/windows/ftpx.ps1 +0 -1
- machineconfig/scripts/windows/fzfb.ps1 +0 -3
- machineconfig/scripts/windows/fzfg.ps1 +0 -2
- machineconfig/scripts/windows/fzfrga.bat +0 -20
- machineconfig/scripts/windows/gpt.ps1 +0 -1
- machineconfig/scripts/windows/grep.ps1 +0 -2
- machineconfig/scripts/windows/initai.ps1 +0 -1
- machineconfig/scripts/windows/kill_process.ps1 +0 -1
- machineconfig/scripts/windows/nano.ps1 +0 -3
- machineconfig/scripts/windows/pomodoro.ps1 +0 -1
- machineconfig/scripts/windows/reload_path.ps1 +0 -3
- machineconfig/scripts/windows/scheduler.ps1 +0 -1
- machineconfig/scripts/windows/sessions.ps1 +0 -1
- machineconfig/scripts/windows/snapshot.ps1 +0 -1
- machineconfig/scripts/windows/start_slidev.ps1 +0 -1
- machineconfig/scripts/windows/start_terminals.ps1 +0 -1
- machineconfig/scripts/windows/wifi_conn.ps1 +0 -2
- machineconfig/scripts/windows/wsl_rdp_windows_port_forwarding.ps1 +0 -46
- machineconfig/scripts/windows/wsl_ssh_windows_port_forwarding.ps1 +0 -76
- machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
- machineconfig/settings/lf/windows/fzf_edit.ps1 +0 -6
- machineconfig/settings/lf/windows/tst.ps1 +0 -1
- machineconfig/settings/yazi/yazi.toml +0 -4
- machineconfig/setup_linux/nix/cli_installation.sh +0 -157
- machineconfig/setup_linux/others/openssh-server_add_pub_key.sh +0 -57
- machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -11
- machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -52
- machineconfig/setup_windows/web_shortcuts/all.ps1 +0 -18
- machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +0 -36
- machineconfig/setup_windows/web_shortcuts/croshell.ps1 +0 -16
- machineconfig/setup_windows/web_shortcuts/ssh.ps1 +0 -11
- machineconfig/utils/ai/generate_file_checklist.py +0 -68
- machineconfig/utils/installer_utils/installer.py +0 -189
- machineconfig-5.15.dist-info/METADATA +0 -188
- machineconfig-5.15.dist-info/RECORD +0 -415
- machineconfig-5.15.dist-info/entry_points.txt +0 -18
- machineconfig/cluster/sessions_managers/{utils → helpers}/load_balancer_helper.py +0 -0
- machineconfig/scripts/linux/{share_nfs → other/share_nfs} +0 -0
- machineconfig/scripts/linux/{start_docker → other/start_docker} +0 -0
- machineconfig/scripts/linux/{switch_ip → other/switch_ip} +0 -0
- machineconfig/{jobs/python → scripts/python/ai/utils}/__init__.py +0 -0
- machineconfig/scripts/python/{helpers → helpers_agents}/__init__.py +0 -0
- machineconfig/{jobs/windows/msc/cli_agents.bat → scripts/python/helpers_agents/agentic_frameworks/__init__.py} +0 -0
- machineconfig/scripts/python/{fire_agents_help_search.py → helpers_agents/fire_agents_help_search.py} +0 -0
- machineconfig/scripts/python/{fire_agents_load_balancer.py → helpers_agents/fire_agents_load_balancer.py} +0 -0
- machineconfig/{jobs/windows/msc/cli_agents.ps1 → scripts/python/helpers_cloud/__init__.py} +0 -0
- machineconfig/scripts/python/{helpers → helpers_cloud}/cloud_helpers.py +1 -1
- /machineconfig/scripts/python/{helpers → helpers_cloud}/helpers5.py +0 -0
- /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → helpers_croshell/__init__.py} +0 -0
- /machineconfig/scripts/python/{pomodoro.py → helpers_croshell/pomodoro.py} +0 -0
- /machineconfig/scripts/python/{scheduler.py → helpers_croshell/scheduler.py} +0 -0
- /machineconfig/scripts/python/{viewer.py → helpers_croshell/viewer.py} +0 -0
- /machineconfig/scripts/python/{viewer_template.py → helpers_croshell/viewer_template.py} +0 -0
- /machineconfig/scripts/{windows/share_nfs.ps1 → python/helpers_devops/__init__.py} +0 -0
- /machineconfig/{settings/shells/pwsh/profile.ps1 → scripts/python/helpers_devops/themes/__init__.py} +0 -0
- /machineconfig/{settings/yazi/keymap.toml → scripts/python/helpers_devops/themes/choose_starship_theme.ps1} +0 -0
- /machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -0
- /machineconfig/scripts/{linux → python/helpers_network}/mount_drive +0 -0
- /machineconfig/scripts/python/{mount_nw_drive.py → helpers_network/mount_nw_drive.py} +0 -0
- /machineconfig/scripts/{linux → python/helpers_network}/mount_smb +0 -0
- /machineconfig/scripts/windows/{mount_nw.ps1 → mounts/mount_nw.ps1} +0 -0
- /machineconfig/scripts/windows/{mount_smb.ps1 → mounts/mount_smb.ps1} +0 -0
- /machineconfig/scripts/windows/{share_cloud.cmd → mounts/share_cloud.cmd} +0 -0
- /machineconfig/scripts/windows/{unlock_bitlocker.ps1 → mounts/unlock_bitlocker.ps1} +0 -0
- /machineconfig/setup_linux/{web_shortcuts → others}/android.sh +0 -0
- /machineconfig/{jobs/windows/archive → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
- /machineconfig/{jobs/windows/archive → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
- {machineconfig-5.15.dist-info → machineconfig-7.98.dist-info}/WHEEL +0 -0
- {machineconfig-5.15.dist-info → machineconfig-7.98.dist-info}/top_level.txt +0 -0
|
@@ -1,189 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
1
|
from git import Repo
|
|
4
|
-
from
|
|
2
|
+
from machineconfig.scripts.python.helpers_repos.repo_analyzer_1 import count_python_lines, get_default_branch
|
|
5
3
|
from datetime import datetime
|
|
6
|
-
|
|
4
|
+
import polars as pl
|
|
7
5
|
from pathlib import Path
|
|
8
|
-
from
|
|
9
|
-
import typer
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from typing import Any, Dict, List, Optional, Union
|
|
14
|
-
import polars as pl
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
app = typer.Typer()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def count_lines_in_commit(commit: "Any") -> int:
|
|
21
|
-
total_lines = 0
|
|
22
|
-
for file in commit.stats.files:
|
|
23
|
-
if str(file).endswith(".py"):
|
|
24
|
-
blob = commit.tree / file
|
|
25
|
-
total_lines += len(blob.data_stream.read().decode("utf-8").splitlines())
|
|
26
|
-
return total_lines
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def count_historical_loc(repo_path: str) -> int:
|
|
30
|
-
repo = Repo(repo_path)
|
|
31
|
-
file_line_counts: "Dict[str, int]" = defaultdict(int)
|
|
32
|
-
total_commits: int = sum(1 for _ in repo.iter_commits())
|
|
33
|
-
print(f"Total commits to process: {total_commits}")
|
|
34
|
-
for i, commit in enumerate(repo.iter_commits(), 1):
|
|
35
|
-
if i % 100 == 0 or i == total_commits:
|
|
36
|
-
print(f"Processing commit {i}/{total_commits} ({i / total_commits:.1%})")
|
|
37
|
-
try:
|
|
38
|
-
# Handle initial commits that have no parents
|
|
39
|
-
if not commit.parents:
|
|
40
|
-
# For initial commit, count all lines in Python files
|
|
41
|
-
for file in commit.stats.files:
|
|
42
|
-
if str(file).endswith(".py"):
|
|
43
|
-
file_line_counts[str(file)] += commit.stats.files[file]["insertions"]
|
|
44
|
-
else:
|
|
45
|
-
# For commits with parents, use stats
|
|
46
|
-
for file in commit.stats.files:
|
|
47
|
-
if str(file).endswith(".py"):
|
|
48
|
-
file_line_counts[str(file)] += commit.stats.files[file]["insertions"]
|
|
49
|
-
except Exception:
|
|
50
|
-
# If stats fail (e.g., corrupted parent), skip this commit
|
|
51
|
-
print(f"Warning: Could not get stats for commit {commit.hexsha[:8]}, skipping")
|
|
52
|
-
continue
|
|
53
|
-
|
|
54
|
-
print(f"\nProcessed files: {len(file_line_counts)}")
|
|
55
|
-
return sum(file_line_counts.values())
|
|
56
|
-
|
|
57
|
-
def count_python_lines(commit: "Any") -> int:
|
|
58
|
-
"""Count total lines in Python files for a specific commit"""
|
|
59
|
-
total_lines = 0
|
|
60
|
-
try:
|
|
61
|
-
for blob in commit.tree.traverse():
|
|
62
|
-
if blob.path.endswith(".py"):
|
|
63
|
-
try:
|
|
64
|
-
content = blob.data_stream.read().decode("utf-8")
|
|
65
|
-
total_lines += len(content.splitlines())
|
|
66
|
-
except Exception as _e:
|
|
67
|
-
continue
|
|
68
|
-
except Exception as _e:
|
|
69
|
-
return 0
|
|
70
|
-
return total_lines
|
|
71
|
-
def get_default_branch(repo: Repo) -> str:
|
|
72
|
-
"""Get the default branch name of the repository"""
|
|
73
|
-
try:
|
|
74
|
-
_ = repo.refs["main"]
|
|
75
|
-
return "main" # First try 'main'
|
|
76
|
-
except IndexError:
|
|
77
|
-
try:
|
|
78
|
-
_ = repo.refs["master"]
|
|
79
|
-
return "master" # Then try 'master'
|
|
80
|
-
except IndexError:
|
|
81
|
-
return repo.head.reference.name # If neither exists, get the branch the HEAD is pointing to
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@app.command()
|
|
85
|
-
def count_historical(repo_path: str = typer.Argument(..., help="Path to the git repository")):
|
|
86
|
-
"""Count total historical lines of Python code in the repository."""
|
|
87
|
-
print(f"Analyzing repository: {repo_path}")
|
|
88
|
-
total_loc: int = count_historical_loc(repo_path)
|
|
89
|
-
print(f"\nTotal historical lines of Python code: {total_loc}")
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@app.command()
|
|
93
|
-
def analyze_over_time(repo_path: str = typer.Argument(..., help="Path to the git repository")):
|
|
94
|
-
"""Analyze a git repository to track Python code size over time with visualization."""
|
|
95
|
-
repo: Repo = Repo(repo_path)
|
|
96
|
-
branch_name: str = get_default_branch(repo)
|
|
97
|
-
print(f"🔍 Using branch: {branch_name}")
|
|
98
|
-
commit_data: "List[Dict[str, Any]]" = []
|
|
99
|
-
print("⏳ Analyzing commits...")
|
|
100
|
-
try:
|
|
101
|
-
commits = list(repo.iter_commits(branch_name))
|
|
102
|
-
from datetime import timezone
|
|
103
|
-
|
|
104
|
-
for commit in track(commits, description="Processing commits..."):
|
|
105
|
-
commit_data.append({"hash": commit.hexsha, "dtmExit": datetime.fromtimestamp(commit.committed_date, tz=timezone.utc), "lines": count_python_lines(commit)})
|
|
106
|
-
except Exception as e:
|
|
107
|
-
print(f"❌ Error analyzing commits: {str(e)}")
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
import polars as pl
|
|
111
|
-
import plotly.graph_objects as go
|
|
112
|
-
|
|
113
|
-
df = pl.DataFrame(commit_data)
|
|
114
|
-
df = df.sort("dtmExit")
|
|
115
|
-
# Create interactive plotly figure with dark theme and all bells and whistles
|
|
116
|
-
fig = go.Figure()
|
|
117
|
-
# Add line chart with gradient fill and sparkle effect
|
|
118
|
-
fig.add_trace(go.Scatter(x=df["dtmExit"], y=df["lines"], mode="lines", line={"width": 3, "color": "#00b4ff"}, fill="tozeroy", fillcolor="rgba(0, 180, 255, 0.2)", name="Lines of Code", hovertemplate="<b>Date:</b> %{x}<br><b>Lines:</b> %{y:,}<extra></extra>"))
|
|
119
|
-
# Add markers for significant points (min, max, last)
|
|
120
|
-
min_idx = df["lines"].arg_min()
|
|
121
|
-
max_idx = df["lines"].arg_max()
|
|
122
|
-
min_point = df.slice(min_idx, 1).to_dicts()[0] if min_idx is not None else {}
|
|
123
|
-
max_point = df.slice(max_idx, 1).to_dicts()[0] if max_idx is not None else {}
|
|
124
|
-
last_point = df.slice(-1, 1).to_dicts()[0]
|
|
125
|
-
|
|
126
|
-
# Add markers for significant points
|
|
127
|
-
fig.add_trace(
|
|
128
|
-
go.Scatter(
|
|
129
|
-
x=[min_point["dtmExit"], max_point["dtmExit"], last_point["dtmExit"]],
|
|
130
|
-
y=[min_point["lines"], max_point["lines"], last_point["lines"]],
|
|
131
|
-
mode="markers",
|
|
132
|
-
marker={"size": [10, 14, 12], "color": ["#ff4f4f", "#4fff4f", "#4f4fff"], "line": {"width": 2, "color": "white"}, "symbol": ["circle", "star", "diamond"]},
|
|
133
|
-
name="Key Points",
|
|
134
|
-
hovertemplate="<b>%{text}</b><br>Date: %{x}<br>Lines: %{y:,}<extra></extra>",
|
|
135
|
-
text=[f"🔽 Min: {min_point['lines']:,} lines", f"🔼 Max: {max_point['lines']:,} lines", f"📊 Current: {last_point['lines']:,} lines"],
|
|
136
|
-
)
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
# Add annotation only for current point
|
|
140
|
-
# annotations = [
|
|
141
|
-
# {"x": last_point['date'], "y": last_point['lines'], "text": f"📊 Current: {last_point['lines']:,} lines", "showarrow": True, "arrowhead": 2, "arrowsize": 1,
|
|
142
|
-
# "arrowwidth": 2, "arrowcolor": "#ffffff", "font": {"size": 14, "color": "#ffffff"}, "bgcolor": "#00b4ff", "bordercolor": "#ffffff",
|
|
143
|
-
# "borderwidth": 1, "borderpad": 4, "ax": 40, "ay": -40}
|
|
144
|
-
# ]
|
|
145
|
-
|
|
146
|
-
# Update layout with dark theme and customizations
|
|
147
|
-
fig.update_layout(
|
|
148
|
-
title={"text": "✨ Python Code Base Size Over Time ✨", "y": 0.95, "x": 0.5, "xanchor": "center", "yanchor": "top", "font": {"size": 24, "color": "white"}},
|
|
149
|
-
xaxis_title="Date 📅",
|
|
150
|
-
yaxis_title="Lines of Code 📝",
|
|
151
|
-
hovermode="closest",
|
|
152
|
-
template="plotly_dark",
|
|
153
|
-
plot_bgcolor="rgba(25, 25, 35, 1)",
|
|
154
|
-
paper_bgcolor="rgba(15, 15, 25, 1)",
|
|
155
|
-
font={"family": "Arial, sans-serif", "size": 14, "color": "white"}, # annotations=annotations,
|
|
156
|
-
autosize=True,
|
|
157
|
-
height=700,
|
|
158
|
-
margin={"l": 80, "r": 80, "t": 100, "b": 80},
|
|
159
|
-
xaxis={"showgrid": True, "gridcolor": "rgba(80, 80, 100, 0.2)", "showline": True, "linecolor": "rgba(200, 200, 255, 0.2)", "tickfont": {"size": 12}},
|
|
160
|
-
yaxis={"showgrid": True, "gridcolor": "rgba(80, 80, 100, 0.2)", "showline": True, "linecolor": "rgba(200, 200, 255, 0.2)", "tickformat": ",", "tickfont": {"size": 12}},
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
# Add range slider for date selection
|
|
164
|
-
fig.update_xaxes(rangeslider_visible=True, rangeslider_thickness=0.05)
|
|
165
|
-
|
|
166
|
-
# Save as interactive HTML and static image
|
|
167
|
-
plot_dir = Path.home().joinpath("tmp_results", "tmp_images", Path(repo_path).name)
|
|
168
|
-
plot_dir.mkdir(parents=True, exist_ok=True)
|
|
169
|
-
|
|
170
|
-
html_path = plot_dir.joinpath("code_size_evolution.html")
|
|
171
|
-
png_path = plot_dir.joinpath("code_size_evolution.png")
|
|
172
|
-
|
|
173
|
-
fig.write_html(html_path, include_plotlyjs="cdn")
|
|
174
|
-
fig.write_image(png_path, width=1200, height=700, scale=2)
|
|
175
|
-
|
|
176
|
-
print(f"🖼️ Interactive plot saved as {html_path}")
|
|
177
|
-
print(f"🖼️ Static image saved as {png_path}")
|
|
178
|
-
# Print statistics
|
|
179
|
-
print("\n📊 Repository Statistics:")
|
|
180
|
-
print(f"📚 Total commits analyzed: {len(df)}")
|
|
181
|
-
print(f"🔙 Initial line count: {df['lines'][-1]:,}")
|
|
182
|
-
print(f"🔜 Final line count: {df['lines'][0]:,}")
|
|
183
|
-
print(f"📈 Net change: {df['lines'][0] - df['lines'][-1]:,} lines")
|
|
6
|
+
from typing import Any, Dict, List, Union, Optional
|
|
184
7
|
|
|
185
8
|
|
|
186
|
-
def
|
|
9
|
+
def print_python_files_by_size_impl(repo_path: str) -> "Union[pl.DataFrame, Exception]":
|
|
187
10
|
import polars as pl
|
|
188
11
|
import plotly.graph_objects as go
|
|
189
12
|
import plotly.express as px
|
|
@@ -227,13 +50,25 @@ def _print_python_files_by_size_impl(repo_path: str) -> "Union[pl.DataFrame, Exc
|
|
|
227
50
|
df = df.sort("lines", descending=True)
|
|
228
51
|
df = df.filter(pl.col("lines") > 0) # Filter out empty files
|
|
229
52
|
|
|
53
|
+
from rich.console import Console
|
|
54
|
+
from rich.table import Table
|
|
55
|
+
|
|
230
56
|
# Add total count
|
|
231
57
|
total_lines = int(df["lines"].sum())
|
|
232
58
|
file_count: int = len(df)
|
|
233
|
-
|
|
59
|
+
console = Console()
|
|
234
60
|
# Print the DataFrame
|
|
235
|
-
print("\n📊 Python Files Line Count (sorted max to min):")
|
|
236
|
-
|
|
61
|
+
console.print("\n📊 Python Files Line Count (sorted max to min):")
|
|
62
|
+
|
|
63
|
+
table = Table(show_header=True, header_style="bold cyan")
|
|
64
|
+
table.add_column("#", justify="right")
|
|
65
|
+
table.add_column("File", overflow="fold")
|
|
66
|
+
table.add_column("Lines", justify="right")
|
|
67
|
+
|
|
68
|
+
for idx, row in enumerate(df.iter_rows(named=True), 1):
|
|
69
|
+
table.add_row(str(idx), row["filename"], f"{row['lines']:,}")
|
|
70
|
+
|
|
71
|
+
console.print(table)
|
|
237
72
|
print(f"\n📁 Total Python files: {file_count}")
|
|
238
73
|
print(f"📝 Total lines of Python code: {total_lines:,}")
|
|
239
74
|
|
|
@@ -335,14 +170,100 @@ def _print_python_files_by_size_impl(repo_path: str) -> "Union[pl.DataFrame, Exc
|
|
|
335
170
|
return Exception(f"❌ Error analyzing repository: {str(e)}")
|
|
336
171
|
|
|
337
172
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
173
|
+
def analyze_over_time(repo_path: str):
|
|
174
|
+
"""Analyze a git repository to track Python code size over time with visualization."""
|
|
175
|
+
repo: Repo = Repo(repo_path)
|
|
176
|
+
branch_name: str = get_default_branch(repo)
|
|
177
|
+
print(f"🔍 Using branch: {branch_name}")
|
|
178
|
+
commit_data: "List[Dict[str, Any]]" = []
|
|
179
|
+
print("⏳ Analyzing commits...")
|
|
180
|
+
try:
|
|
181
|
+
commits = list(repo.iter_commits(branch_name))
|
|
182
|
+
from datetime import timezone
|
|
183
|
+
from rich.progress import track
|
|
184
|
+
for commit in track(commits, description="Processing commits..."):
|
|
185
|
+
commit_data.append({"hash": commit.hexsha, "dtmExit": datetime.fromtimestamp(commit.committed_date, tz=timezone.utc), "lines": count_python_lines(commit)})
|
|
186
|
+
except Exception as e:
|
|
187
|
+
print(f"❌ Error analyzing commits: {str(e)}")
|
|
344
188
|
return
|
|
345
189
|
|
|
190
|
+
import polars as pl
|
|
191
|
+
import plotly.graph_objects as go
|
|
192
|
+
|
|
193
|
+
df = pl.DataFrame(commit_data)
|
|
194
|
+
df = df.sort("dtmExit")
|
|
195
|
+
# Create interactive plotly figure with dark theme and all bells and whistles
|
|
196
|
+
fig = go.Figure()
|
|
197
|
+
# Add line chart with gradient fill and sparkle effect
|
|
198
|
+
fig.add_trace(go.Scatter(x=df["dtmExit"], y=df["lines"], mode="lines", line={"width": 3, "color": "#00b4ff"}, fill="tozeroy", fillcolor="rgba(0, 180, 255, 0.2)", name="Lines of Code", hovertemplate="<b>Date:</b> %{x}<br><b>Lines:</b> %{y:,}<extra></extra>"))
|
|
199
|
+
# Add markers for significant points (min, max, last)
|
|
200
|
+
min_idx = df["lines"].arg_min()
|
|
201
|
+
max_idx = df["lines"].arg_max()
|
|
202
|
+
min_point = df.slice(min_idx, 1).to_dicts()[0] if min_idx is not None else {}
|
|
203
|
+
max_point = df.slice(max_idx, 1).to_dicts()[0] if max_idx is not None else {}
|
|
204
|
+
last_point = df.slice(-1, 1).to_dicts()[0]
|
|
205
|
+
|
|
206
|
+
# Add markers for significant points
|
|
207
|
+
fig.add_trace(
|
|
208
|
+
go.Scatter(
|
|
209
|
+
x=[min_point["dtmExit"], max_point["dtmExit"], last_point["dtmExit"]],
|
|
210
|
+
y=[min_point["lines"], max_point["lines"], last_point["lines"]],
|
|
211
|
+
mode="markers",
|
|
212
|
+
marker={"size": [10, 14, 12], "color": ["#ff4f4f", "#4fff4f", "#4f4fff"], "line": {"width": 2, "color": "white"}, "symbol": ["circle", "star", "diamond"]},
|
|
213
|
+
name="Key Points",
|
|
214
|
+
hovertemplate="<b>%{text}</b><br>Date: %{x}<br>Lines: %{y:,}<extra></extra>",
|
|
215
|
+
text=[f"🔽 Min: {min_point['lines']:,} lines", f"🔼 Max: {max_point['lines']:,} lines", f"📊 Current: {last_point['lines']:,} lines"],
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Add annotation only for current point
|
|
220
|
+
# annotations = [
|
|
221
|
+
# {"x": last_point['date'], "y": last_point['lines'], "text": f"📊 Current: {last_point['lines']:,} lines", "showarrow": True, "arrowhead": 2, "arrowsize": 1,
|
|
222
|
+
# "arrowwidth": 2, "arrowcolor": "#ffffff", "font": {"size": 14, "color": "#ffffff"}, "bgcolor": "#00b4ff", "bordercolor": "#ffffff",
|
|
223
|
+
# "borderwidth": 1, "borderpad": 4, "ax": 40, "ay": -40}
|
|
224
|
+
# ]
|
|
346
225
|
|
|
347
|
-
|
|
348
|
-
|
|
226
|
+
# Update layout with dark theme and customizations
|
|
227
|
+
fig.update_layout(
|
|
228
|
+
title={"text": "✨ Python Code Base Size Over Time ✨", "y": 0.95, "x": 0.5, "xanchor": "center", "yanchor": "top", "font": {"size": 24, "color": "white"}},
|
|
229
|
+
xaxis_title="Date 📅",
|
|
230
|
+
yaxis_title="Lines of Code 📝",
|
|
231
|
+
hovermode="closest",
|
|
232
|
+
template="plotly_dark",
|
|
233
|
+
plot_bgcolor="rgba(25, 25, 35, 1)",
|
|
234
|
+
paper_bgcolor="rgba(15, 15, 25, 1)",
|
|
235
|
+
font={"family": "Arial, sans-serif", "size": 14, "color": "white"}, # annotations=annotations,
|
|
236
|
+
autosize=True,
|
|
237
|
+
height=700,
|
|
238
|
+
margin={"l": 80, "r": 80, "t": 100, "b": 80},
|
|
239
|
+
xaxis={"showgrid": True, "gridcolor": "rgba(80, 80, 100, 0.2)", "showline": True, "linecolor": "rgba(200, 200, 255, 0.2)", "tickfont": {"size": 12}},
|
|
240
|
+
yaxis={"showgrid": True, "gridcolor": "rgba(80, 80, 100, 0.2)", "showline": True, "linecolor": "rgba(200, 200, 255, 0.2)", "tickformat": ",", "tickfont": {"size": 12}},
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Add range slider for date selection
|
|
244
|
+
fig.update_xaxes(rangeslider_visible=True, rangeslider_thickness=0.05)
|
|
245
|
+
|
|
246
|
+
# Save as interactive HTML and static image
|
|
247
|
+
plot_dir = Path.home().joinpath("tmp_results", "tmp_images", Path(repo_path).name)
|
|
248
|
+
plot_dir.mkdir(parents=True, exist_ok=True)
|
|
249
|
+
|
|
250
|
+
html_path = plot_dir.joinpath("code_size_evolution.html")
|
|
251
|
+
png_path = plot_dir.joinpath("code_size_evolution.png")
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
fig.write_html(html_path, include_plotlyjs="cdn")
|
|
255
|
+
except Exception as e:
|
|
256
|
+
print(f"❌ Error saving HTML plot: {str(e)}")
|
|
257
|
+
try:
|
|
258
|
+
fig.write_image(png_path, width=1200, height=700, scale=2)
|
|
259
|
+
except Exception as e:
|
|
260
|
+
print(f"❌ Error saving PNG plot: {str(e)}")
|
|
261
|
+
|
|
262
|
+
print(f"🖼️ Interactive plot saved as {html_path}")
|
|
263
|
+
print(f"🖼️ Static image saved as {png_path}")
|
|
264
|
+
# Print statistics
|
|
265
|
+
print("\n📊 Repository Statistics:")
|
|
266
|
+
print(f"📚 Total commits analyzed: {len(df)}")
|
|
267
|
+
print(f"🔙 Initial line count: {df['lines'][-1]:,}")
|
|
268
|
+
print(f"🔜 Final line count: {df['lines'][0]:,}")
|
|
269
|
+
print(f"📈 Net change: {df['lines'][0] - df['lines'][-1]:,} lines")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
2
|
+
import platform
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_zellij_cmd(wd1: Path, wd2: Path) -> str:
|
|
11
|
+
_ = wd1, wd2
|
|
12
|
+
lines = [
|
|
13
|
+
""" zellij action new-tab --name gitdiff""",
|
|
14
|
+
"""zellij action new-pane --direction down --name local --cwd ./data """,
|
|
15
|
+
"""zellij action write-chars "cd '{wd1}'; git status" """,
|
|
16
|
+
"""zellij action move-focus up; zellij action close-pane """,
|
|
17
|
+
"""zellij action new-pane --direction down --name remote --cwd code """,
|
|
18
|
+
"""zellij action write-chars "cd '{wd2}' """,
|
|
19
|
+
"""git status" """,
|
|
20
|
+
]
|
|
21
|
+
return "; ".join(lines)
|
|
22
|
+
|
|
23
|
+
def delete_remote_repo_copy_and_push_local(remote_repo: str, local_repo: str, cloud: str):
|
|
24
|
+
console.print(Panel("🗑️ Deleting remote repo copy and pushing local copy", title="[bold blue]Repo Sync[/bold blue]", border_style="blue"))
|
|
25
|
+
repo_sync_root = PathExtended(remote_repo).expanduser().absolute()
|
|
26
|
+
repo_root_path = PathExtended(local_repo).expanduser().absolute()
|
|
27
|
+
repo_sync_root.delete(sure=True)
|
|
28
|
+
print("🧹 Removed temporary remote copy")
|
|
29
|
+
from git.remote import Remote
|
|
30
|
+
from git.repo import Repo
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
Remote.remove(Repo(repo_root_path), "originEnc")
|
|
34
|
+
console.print(Panel("🔗 Removed originEnc remote reference", border_style="blue"))
|
|
35
|
+
except Exception:
|
|
36
|
+
pass # type: ignore
|
|
37
|
+
console.print(Panel("📈 Deleting remote repository copy and pushing local changes", width=150, border_style="blue"))
|
|
38
|
+
|
|
39
|
+
repo_root_path.to_cloud(cloud=cloud, zip=True, encrypt=True, rel2home=True, os_specific=False)
|
|
40
|
+
|
|
41
|
+
console.print(Panel("✅ Repository successfully pushed to cloud", title="[bold green]Repo Sync[/bold green]", border_style="green"))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_wt_cmd(wd1: PathExtended, wd2: PathExtended) -> str:
|
|
46
|
+
lines = [
|
|
47
|
+
f"""wt --window 0 new-tab --profile pwsh --title "gitdiff" --tabColor `#3b04d1 --startingDirectory {wd1} ` --colorScheme "Solarized Dark" """,
|
|
48
|
+
f"""split-pane --horizontal --profile pwsh --startingDirectory {wd2} --size 0.5 --colorScheme "Tango Dark" -- pwsh -Interactive """,
|
|
49
|
+
]
|
|
50
|
+
return " `; ".join(lines)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def inspect_repos(repo_local_root: str, repo_remote_root: str):
|
|
54
|
+
console.print(Panel(f"📂 Local: {repo_local_root}\n📂 Remote: {repo_remote_root}", title="[bold blue]🔍 Inspecting Repositories[/bold blue]", border_style="blue"))
|
|
55
|
+
|
|
56
|
+
if platform.system() == "Windows":
|
|
57
|
+
program = get_wt_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_local_root))
|
|
58
|
+
elif platform.system() in ["Linux", "Darwin"]:
|
|
59
|
+
program = get_zellij_cmd(wd1=PathExtended(repo_local_root), wd2=PathExtended(repo_remote_root))
|
|
60
|
+
else:
|
|
61
|
+
raise NotImplementedError(f"Platform {platform.system()} not implemented.")
|
|
62
|
+
import tempfile
|
|
63
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix=".sh" if platform.system() != "Windows" else ".ps1", delete=False, encoding='utf-8') as temp_file:
|
|
64
|
+
temp_file.write(program)
|
|
65
|
+
temp_script_path = PathExtended(temp_file.name)
|
|
66
|
+
console.print(Panel(f"🚀 Launching repo inspection tool...\n\n[blue]{temp_script_path}[/blue]", border_style="blue"))
|
|
@@ -45,7 +45,7 @@ def run_uv_sync(repo_path: Path) -> bool:
|
|
|
45
45
|
try:
|
|
46
46
|
print(f"🔄 Running uv sync in {repo_path}")
|
|
47
47
|
# Run uv sync with output directly to terminal (no capture)
|
|
48
|
-
subprocess.run(["uv", "sync"], cwd=repo_path, check=True)
|
|
48
|
+
subprocess.run(["uv", "sync", "--no-dev"], cwd=repo_path, check=True)
|
|
49
49
|
print("✅ uv sync completed successfully")
|
|
50
50
|
return True
|
|
51
51
|
except subprocess.CalledProcessError as e:
|
|
@@ -65,7 +65,7 @@ def get_file_hash(file_path: Path) -> str | None:
|
|
|
65
65
|
return hashlib.sha256(file_path.read_bytes()).hexdigest()
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def update_repository(repo: git.Repo,
|
|
68
|
+
def update_repository(repo: git.Repo, auto_uv_sync: bool, allow_password_prompt: bool) -> RepositoryUpdateResult:
|
|
69
69
|
"""Update a single repository and return detailed information about what happened."""
|
|
70
70
|
repo_path = Path(repo.working_dir)
|
|
71
71
|
print(f"🔄 {'Updating ' + str(repo_path):.^80}")
|
|
@@ -253,7 +253,7 @@ def update_repository(repo: git.Repo, auto_sync: bool, allow_password_prompt: bo
|
|
|
253
253
|
print(f"✅ Set permissions for {lf_exe_path}")
|
|
254
254
|
|
|
255
255
|
# Run uv sync if dependencies changed and auto_sync is enabled
|
|
256
|
-
if result["dependencies_changed"] and
|
|
256
|
+
if result["dependencies_changed"] and auto_uv_sync:
|
|
257
257
|
result["uv_sync_ran"] = True
|
|
258
258
|
result["uv_sync_success"] = run_uv_sync(repo_path)
|
|
259
259
|
|
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Annotated
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_from_function(
|
|
8
|
+
num_process: Annotated[int, typer.Option(..., "--num-process", "-n", help="Number of parallel processes to run")],
|
|
9
|
+
path: Annotated[str, typer.Option(..., "--path", "-p", help="Path to a Python or Shell script file or a directory containing such files")] = ".",
|
|
10
|
+
function: Annotated[Optional[str], typer.Option(..., "--function", "-f", help="Function to run from the Python file. If not provided, you will be prompted to choose.")] = None,
|
|
11
|
+
):
|
|
12
|
+
from machineconfig.utils.ve import get_ve_path_and_ipython_profile
|
|
13
|
+
from machineconfig.utils.options import choose_from_options
|
|
14
|
+
from machineconfig.utils.path_helper import match_file_name, sanitize_path
|
|
15
|
+
from machineconfig.utils.accessories import get_repo_root
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
path_obj = sanitize_path(path)
|
|
20
|
+
if not path_obj.exists():
|
|
21
|
+
suffixes = {".py"}
|
|
22
|
+
choice_file = match_file_name(sub_string=path, search_root=Path.cwd(), suffixes=suffixes)
|
|
23
|
+
elif path_obj.is_dir():
|
|
24
|
+
from machineconfig.utils.path_helper import search_for_files_of_interest
|
|
25
|
+
print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
|
|
26
|
+
files = search_for_files_of_interest(path_obj, suffixes={".py", ".sh", ".ps1"})
|
|
27
|
+
print(f"🔍 Got #{len(files)} results.")
|
|
28
|
+
choice_file = choose_from_options(multi=False, options=files, tv=True, msg="Choose one option")
|
|
29
|
+
choice_file = Path(choice_file)
|
|
30
|
+
else:
|
|
31
|
+
choice_file = path_obj
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
repo_root = get_repo_root(Path(choice_file))
|
|
35
|
+
print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
|
|
36
|
+
ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
|
|
37
|
+
if ipy_profile is None:
|
|
38
|
+
ipy_profile = "default"
|
|
39
|
+
# if ve_root_from_file is None:
|
|
40
|
+
# raise ValueError(f"Could not determine virtual environment for file {choice_file}. Please ensure it is within a recognized project structure.")
|
|
41
|
+
# _activate_ve_line = get_ve_activate_line(ve_root=ve_root_from_file)
|
|
42
|
+
if ve_root_from_file is not None:
|
|
43
|
+
start_dir = Path(ve_root_from_file).parent
|
|
44
|
+
else:
|
|
45
|
+
start_dir = Path.cwd()
|
|
46
|
+
|
|
47
|
+
# ========================= choosing function to run
|
|
48
|
+
if function is None or function.strip() == "":
|
|
49
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import choose_function_or_lines
|
|
50
|
+
choice_function, choice_file, _kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict={})
|
|
51
|
+
else:
|
|
52
|
+
choice_function = function
|
|
53
|
+
|
|
54
|
+
from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
|
|
55
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
56
|
+
layout: LayoutConfig = {"layoutName": "fireNprocess", "layoutTabs": []}
|
|
57
|
+
for an_arg in range(num_process):
|
|
58
|
+
layout["layoutTabs"].append({
|
|
59
|
+
"tabName": f"tab{an_arg}",
|
|
60
|
+
"startDir": str(start_dir),
|
|
61
|
+
"command": f"uv run python -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={num_process}"
|
|
62
|
+
})
|
|
63
|
+
print(layout)
|
|
64
|
+
run_zellij_layout(layout_config=layout)
|
|
65
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Optional
|
|
4
|
+
import typer
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def download(
|
|
9
|
+
url: Annotated[Optional[str], typer.Argument(..., help="The URL to download the file from.")] = None,
|
|
10
|
+
decompress: Annotated[bool, typer.Option(..., "--decompress", "-d", help="Decompress the file if it's an archive.")] = False,
|
|
11
|
+
output: Annotated[Optional[str], typer.Option("--output", "-o", help="The output file path.")] = None,
|
|
12
|
+
output_dir: Annotated[Optional[str], typer.Option("--output-dir", help="Directory to place the downloaded file in.")] = None,
|
|
13
|
+
) -> Optional["Path"]:
|
|
14
|
+
import subprocess
|
|
15
|
+
from urllib.parse import parse_qs, unquote, urlparse
|
|
16
|
+
from requests import Response
|
|
17
|
+
import requests
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
if url is None:
|
|
20
|
+
typer.echo("❌ Error: URL is required.", err=True)
|
|
21
|
+
return None
|
|
22
|
+
if output is not None and output_dir is not None:
|
|
23
|
+
typer.echo("❌ Error: --output and --output-dir cannot be used together.", err=True)
|
|
24
|
+
return None
|
|
25
|
+
typer.echo(f"📥 Downloading from: {url}")
|
|
26
|
+
|
|
27
|
+
def _sanitize_candidate_filename(name: str) -> Optional[str]:
|
|
28
|
+
candidate = Path(name).name.strip()
|
|
29
|
+
if not candidate or candidate in {".", ".."}:
|
|
30
|
+
return None
|
|
31
|
+
return candidate
|
|
32
|
+
|
|
33
|
+
def _filename_from_content_disposition(header_value: Optional[str]) -> Optional[str]:
|
|
34
|
+
if header_value is None:
|
|
35
|
+
return None
|
|
36
|
+
parts = [segment.strip() for segment in header_value.split(";")]
|
|
37
|
+
for part in parts:
|
|
38
|
+
lower = part.lower()
|
|
39
|
+
if lower.startswith("filename*="):
|
|
40
|
+
value = part.split("=", 1)[1]
|
|
41
|
+
value = value.strip().strip('"')
|
|
42
|
+
if "''" in value:
|
|
43
|
+
value = value.split("''", 1)[1]
|
|
44
|
+
decoded = unquote(value)
|
|
45
|
+
sanitized = _sanitize_candidate_filename(decoded)
|
|
46
|
+
if sanitized is not None:
|
|
47
|
+
return sanitized
|
|
48
|
+
if lower.startswith("filename="):
|
|
49
|
+
value = part.split("=", 1)[1].strip().strip('"')
|
|
50
|
+
decoded = unquote(value)
|
|
51
|
+
sanitized = _sanitize_candidate_filename(decoded)
|
|
52
|
+
if sanitized is not None:
|
|
53
|
+
return sanitized
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def _filename_from_url(source_url: str) -> Optional[str]:
|
|
57
|
+
parsed = urlparse(source_url)
|
|
58
|
+
url_candidate = _sanitize_candidate_filename(unquote(Path(parsed.path).name))
|
|
59
|
+
if url_candidate is not None:
|
|
60
|
+
return url_candidate
|
|
61
|
+
query_params = parse_qs(parsed.query, keep_blank_values=True)
|
|
62
|
+
for key, values in query_params.items():
|
|
63
|
+
lower_key = key.lower()
|
|
64
|
+
if "name" in lower_key or "file" in lower_key:
|
|
65
|
+
for value in values:
|
|
66
|
+
sanitized = _sanitize_candidate_filename(unquote(value))
|
|
67
|
+
if sanitized is not None:
|
|
68
|
+
return sanitized
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def _resolve_download_path(request_url: str, response: Response, requested_output: Optional[str], requested_output_dir: Optional[str]) -> Path:
|
|
72
|
+
if requested_output is not None:
|
|
73
|
+
return Path(requested_output)
|
|
74
|
+
header_candidate = _filename_from_content_disposition(response.headers.get("content-disposition"))
|
|
75
|
+
if header_candidate is None:
|
|
76
|
+
header_candidate = _filename_from_url(response.url)
|
|
77
|
+
if header_candidate is None:
|
|
78
|
+
header_candidate = _filename_from_url(request_url)
|
|
79
|
+
if header_candidate is None:
|
|
80
|
+
header_candidate = "downloaded_file"
|
|
81
|
+
if requested_output_dir is not None:
|
|
82
|
+
return Path(requested_output_dir) / header_candidate
|
|
83
|
+
return Path(header_candidate)
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
with requests.get(url, allow_redirects=True, stream=True, timeout=60) as response:
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
download_path = _resolve_download_path(url, response, output, output_dir)
|
|
89
|
+
download_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
total_size_header = response.headers.get("content-length", "0")
|
|
91
|
+
try:
|
|
92
|
+
total_size = int(total_size_header)
|
|
93
|
+
except (TypeError, ValueError):
|
|
94
|
+
total_size = 0
|
|
95
|
+
if total_size <= 0:
|
|
96
|
+
with open(download_path, "wb") as file_handle:
|
|
97
|
+
file_handle.write(response.content)
|
|
98
|
+
else:
|
|
99
|
+
downloaded = 0
|
|
100
|
+
chunk_size = 8192 * 40
|
|
101
|
+
with open(download_path, "wb") as file_handle:
|
|
102
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
103
|
+
if not chunk:
|
|
104
|
+
continue
|
|
105
|
+
file_handle.write(chunk)
|
|
106
|
+
downloaded += len(chunk)
|
|
107
|
+
progress = (downloaded / total_size) * 100
|
|
108
|
+
typer.echo(f"\r⏬ Progress: {progress:.1f}% ({downloaded}/{total_size} bytes)", nl=False)
|
|
109
|
+
typer.echo()
|
|
110
|
+
except requests.exceptions.RequestException as exception:
|
|
111
|
+
typer.echo(f"❌ Download failed: {exception}", err=True)
|
|
112
|
+
return None
|
|
113
|
+
except OSError as exception:
|
|
114
|
+
typer.echo(f"❌ File write error: {exception}", err=True)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
typer.echo(f"✅ Downloaded to: {download_path}")
|
|
118
|
+
result_path: Path = download_path
|
|
119
|
+
if decompress:
|
|
120
|
+
typer.echo(f"📦 Decompressing: {download_path}")
|
|
121
|
+
base_name = download_path.name.split(".", maxsplit=1)[0] # ouch decompresses all (e.g. .tar.gz) in one go.
|
|
122
|
+
if base_name in {"", ".", ".."}:
|
|
123
|
+
base_name = "extracted"
|
|
124
|
+
extract_dir = download_path.parent / base_name
|
|
125
|
+
extract_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
try:
|
|
127
|
+
subprocess.run(
|
|
128
|
+
["ouch", "decompress", str(download_path), "--dir", str(extract_dir)],
|
|
129
|
+
check=True,
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
)
|
|
133
|
+
typer.echo(f"✅ Decompressed to: {extract_dir}")
|
|
134
|
+
if download_path.exists():
|
|
135
|
+
download_path.unlink()
|
|
136
|
+
typer.echo(f"🗑️ Removed archive: {download_path}")
|
|
137
|
+
result_path = extract_dir
|
|
138
|
+
except subprocess.CalledProcessError as exception:
|
|
139
|
+
typer.echo(f"❌ Decompression failed: {exception.stderr}", err=True)
|
|
140
|
+
return None
|
|
141
|
+
except FileNotFoundError:
|
|
142
|
+
typer.echo("❌ Error: ouch command not found. Please install ouch.", err=True)
|
|
143
|
+
typer.echo("💡 Install with: cargo install ouch", err=True)
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
return result_path.resolve()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
pass
|