| 1 | // Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module main |
| 5 | |
| 6 | import os |
| 7 | import os.cmdline |
| 8 | import rand |
| 9 | import v.help |
| 10 | import v.vmod |
| 11 | |
| 12 | const server_url_option_names = ['-m', '--mirror', '-server-url', '--server-url', '--server-urls'] |
| 13 | const settings = init_settings() |
| 14 | const default_vpm_server_urls = ['https://vpm.vlang.io', 'https://vpm.url4e.com'] |
| 15 | const vpm_server_urls = rand.shuffle_clone(default_vpm_server_urls) or { [] } // ensure that all queries are distributed fairly |
| 16 | const valid_vpm_commands = ['help', 'search', 'install', 'link', 'update', 'upgrade', 'outdated', |
| 17 | 'list', 'remove', 'show', 'unlink'] |
| 18 | const excluded_dirs = ['.cache', 'vlib'] |
| 19 | |
| 20 | fn main() { |
| 21 | unbuffer_stdout() |
| 22 | os.setenv(selected_server_url_env, '', true) |
| 23 | // This tool is intended to be launched by the v frontend, |
| 24 | // which provides the path to V inside os.getenv('VEXE') |
| 25 | // args are: vpm [options] SUBCOMMAND module names |
| 26 | vpm_log_header('vpm start') |
| 27 | defer { |
| 28 | vpm_log_header('vpm exit') |
| 29 | } |
| 30 | args := os.args[1..] |
| 31 | params := cmdline.only_non_options(args) |
| 32 | vpm_log(@FILE_LINE, @FN, 'params: ${params}') |
| 33 | if params.len < 1 { |
| 34 | help.print_and_exit('vpm', exit_code: 5) |
| 35 | } |
| 36 | vpm_command := parse_vpm_command(args) |
| 37 | if vpm_command == '' { |
| 38 | help.print_and_exit('vpm', exit_code: 5) |
| 39 | } |
| 40 | mut query := parse_query_args(args, vpm_command) |
| 41 | vpm_log(@FILE_LINE, @FN, 'query: ${query}') |
| 42 | ensure_vmodules_dir_exist() |
| 43 | match vpm_command { |
| 44 | 'help' { |
| 45 | help.print_and_exit('vpm') |
| 46 | } |
| 47 | 'search' { |
| 48 | vpm_search(query) |
| 49 | } |
| 50 | 'install' { |
| 51 | vpm_install(query) |
| 52 | } |
| 53 | 'link' { |
| 54 | vpm_link(query) |
| 55 | } |
| 56 | 'update' { |
| 57 | vpm_update(query) |
| 58 | } |
| 59 | 'upgrade' { |
| 60 | vpm_upgrade() |
| 61 | } |
| 62 | 'outdated' { |
| 63 | vpm_outdated() |
| 64 | } |
| 65 | 'list' { |
| 66 | vpm_list() |
| 67 | } |
| 68 | 'remove' { |
| 69 | vpm_remove(query) |
| 70 | } |
| 71 | 'show' { |
| 72 | vpm_show(query) |
| 73 | } |
| 74 | 'unlink' { |
| 75 | vpm_unlink(query) |
| 76 | } |
| 77 | else { |
| 78 | // Unreachable in regular usage. V will catch unknown commands beforehand. |
| 79 | vpm_error('unknown command "${vpm_command}"') |
| 80 | help.print_and_exit('vpm', exit_code: 3) |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | fn parse_vpm_command(args []string) string { |
| 86 | mut skip_next := false |
| 87 | for arg in args { |
| 88 | if skip_next { |
| 89 | skip_next = false |
| 90 | continue |
| 91 | } |
| 92 | if arg in server_url_option_names { |
| 93 | skip_next = true |
| 94 | continue |
| 95 | } |
| 96 | if arg in valid_vpm_commands { |
| 97 | return arg |
| 98 | } |
| 99 | } |
| 100 | return '' |
| 101 | } |
| 102 | |
| 103 | fn parse_query_args(args []string, vpm_command string) []string { |
| 104 | mut query := []string{} |
| 105 | mut skip_next := false |
| 106 | mut has_found_command := false |
| 107 | for arg in args { |
| 108 | if skip_next { |
| 109 | skip_next = false |
| 110 | continue |
| 111 | } |
| 112 | if arg in server_url_option_names { |
| 113 | skip_next = true |
| 114 | continue |
| 115 | } |
| 116 | if !has_found_command { |
| 117 | if arg == vpm_command { |
| 118 | has_found_command = true |
| 119 | } |
| 120 | continue |
| 121 | } |
| 122 | if arg.starts_with('-') { |
| 123 | continue |
| 124 | } |
| 125 | query << arg |
| 126 | } |
| 127 | return query |
| 128 | } |
| 129 | |
| 130 | fn vpm_upgrade() { |
| 131 | outdated := get_outdated() |
| 132 | if outdated.len > 0 { |
| 133 | vpm_update(outdated) |
| 134 | } else { |
| 135 | println('Modules are up to date.') |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | fn vpm_list() { |
| 140 | installed := get_installed_modules() |
| 141 | if installed.len == 0 { |
| 142 | println('You have no modules installed.') |
| 143 | exit(0) |
| 144 | } |
| 145 | for m in installed { |
| 146 | println(m) |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | fn vpm_remove(query []string) { |
| 151 | if settings.is_help { |
| 152 | help.print_and_exit('remove') |
| 153 | } |
| 154 | if query.len == 0 { |
| 155 | vpm_error('specify at least one module name for removal.') |
| 156 | exit(2) |
| 157 | } |
| 158 | for m in query { |
| 159 | final_module_path := get_path_of_existing_module(m) or { continue } |
| 160 | println('Removing module "${m}" from ${fmt_mod_path(final_module_path)} ...') |
| 161 | vpm_log(@FILE_LINE, @FN, 'removing: ${final_module_path}') |
| 162 | rmdir_all(final_module_path) or { vpm_error(err.msg(), verbose: true) } |
| 163 | // Delete author directory if it is empty. |
| 164 | author := normalize_mod_path(m.split('.')[0]) |
| 165 | author_dir := os.real_path(os.join_path(settings.vmodules_path, author)) |
| 166 | if !os.exists(author_dir) { |
| 167 | continue |
| 168 | } |
| 169 | if os.is_dir_empty(author_dir) { |
| 170 | verbose_println('Removing author folder ${author_dir}') |
| 171 | rmdir_all(author_dir) or { vpm_error(err.msg(), verbose: true) } |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | fn vpm_show(query []string) { |
| 177 | installed_modules := get_installed_modules() |
| 178 | for m in query { |
| 179 | if m !in installed_modules { |
| 180 | info := get_mod_vpm_info(m) or { continue } |
| 181 | print('Name: ${info.name} |
| 182 | Homepage: ${info.url} |
| 183 | Downloads: ${info.nr_downloads} |
| 184 | Installed: False |
| 185 | -------- |
| 186 | ') |
| 187 | continue |
| 188 | } |
| 189 | path := os.join_path(settings.vmodules_path, m.replace('.', os.path_separator)) |
| 190 | mod := vmod.from_file(os.join_path(path, 'v.mod')) or { continue } |
| 191 | print('Name: ${mod.name} |
| 192 | Version: ${mod.version} |
| 193 | Description: ${mod.description} |
| 194 | Homepage: ${mod.repo_url} |
| 195 | Author: ${mod.author} |
| 196 | License: ${mod.license} |
| 197 | Location: ${path} |
| 198 | Requires: ${mod.dependencies.join(', ')} |
| 199 | -------- |
| 200 | ') |
| 201 | } |
| 202 | } |
| 203 | |