v / cmd / tools / vpm / vpm.v
202 lines · 193 sloc · 4.64 KB · 9317f4b5078c90aee517b362099900f8f34c21ef
Raw
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.
4module main
5
6import os
7import os.cmdline
8import rand
9import v.help
10import v.vmod
11
12const server_url_option_names = ['-m', '--mirror', '-server-url', '--server-url', '--server-urls']
13const settings = init_settings()
14const default_vpm_server_urls = ['https://vpm.vlang.io', 'https://vpm.url4e.com']
15const vpm_server_urls = rand.shuffle_clone(default_vpm_server_urls) or { [] } // ensure that all queries are distributed fairly
16const valid_vpm_commands = ['help', 'search', 'install', 'link', 'update', 'upgrade', 'outdated',
17 'list', 'remove', 'show', 'unlink']
18const excluded_dirs = ['.cache', 'vlib']
19
20fn 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
85fn 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
103fn 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
130fn 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
139fn 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
150fn 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
176fn 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}
182Homepage: ${info.url}
183Downloads: ${info.nr_downloads}
184Installed: 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}
192Version: ${mod.version}
193Description: ${mod.description}
194Homepage: ${mod.repo_url}
195Author: ${mod.author}
196License: ${mod.license}
197Location: ${path}
198Requires: ${mod.dependencies.join(', ')}
199--------
200')
201 }
202}
203