aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/old/cmd/tools/vpm.v
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/old/cmd/tools/vpm.v')
-rw-r--r--v_windows/v/old/cmd/tools/vpm.v601
1 files changed, 601 insertions, 0 deletions
diff --git a/v_windows/v/old/cmd/tools/vpm.v b/v_windows/v/old/cmd/tools/vpm.v
new file mode 100644
index 0000000..a503d24
--- /dev/null
+++ b/v_windows/v/old/cmd/tools/vpm.v
@@ -0,0 +1,601 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module main
+
+import os
+import os.cmdline
+import net.http
+import json
+import vhelp
+import v.vmod
+
+const (
+ default_vpm_server_urls = ['https://vpm.vlang.io']
+ valid_vpm_commands = ['help', 'search', 'install', 'update', 'upgrade', 'outdated',
+ 'list', 'remove', 'show']
+ excluded_dirs = ['cache', 'vlib']
+ supported_vcs_systems = ['git', 'hg']
+ supported_vcs_folders = ['.git', '.hg']
+ supported_vcs_update_cmds = map{
+ 'git': 'git pull'
+ 'hg': 'hg pull --update'
+ }
+ supported_vcs_install_cmds = map{
+ 'git': 'git clone --depth=1'
+ 'hg': 'hg clone'
+ }
+ supported_vcs_outdated_steps = map{
+ 'git': ['git fetch', 'git rev-parse @', 'git rev-parse @{u}']
+ 'hg': ['hg incoming']
+ }
+)
+
+struct Mod {
+ id int
+ name string
+ url string
+ nr_downloads int
+ vcs string
+}
+
+struct Vmod {
+mut:
+ name string
+ version string
+ deps []string
+}
+
+fn main() {
+ init_settings()
+ // This tool is intended to be launched by the v frontend,
+ // which provides the path to V inside os.getenv('VEXE')
+ // args are: vpm [options] SUBCOMMAND module names
+ params := cmdline.only_non_options(os.args[1..])
+ verbose_println('cli params: $params')
+ if params.len < 1 {
+ vpm_help()
+ exit(5)
+ }
+ vpm_command := params[0]
+ mut module_names := params[1..]
+ ensure_vmodules_dir_exist()
+ // println('module names: ') println(module_names)
+ match vpm_command {
+ 'help' {
+ vpm_help()
+ }
+ 'search' {
+ vpm_search(module_names)
+ }
+ 'install' {
+ if module_names.len == 0 && os.exists('./v.mod') {
+ println('Detected v.mod file inside the project directory. Using it...')
+ manifest := vmod.from_file('./v.mod') or { panic(err) }
+ module_names = manifest.dependencies
+ }
+ vpm_install(module_names)
+ }
+ 'update' {
+ vpm_update(module_names)
+ }
+ 'upgrade' {
+ vpm_upgrade()
+ }
+ 'outdated' {
+ vpm_outdated()
+ }
+ 'list' {
+ vpm_list()
+ }
+ 'remove' {
+ vpm_remove(module_names)
+ }
+ 'show' {
+ vpm_show(module_names)
+ }
+ else {
+ println('Error: you tried to run "v $vpm_command"')
+ println('... but the v package management tool vpm only knows about these commands:')
+ for validcmd in valid_vpm_commands {
+ println(' v $validcmd')
+ }
+ exit(3)
+ }
+ }
+}
+
+fn vpm_search(keywords []string) {
+ search_keys := keywords.map(it.replace('_', '-'))
+ if settings.is_help {
+ vhelp.show_topic('search')
+ exit(0)
+ }
+ if search_keys.len == 0 {
+ println('´v search´ requires *at least one* keyword.')
+ exit(2)
+ }
+ modules := get_all_modules()
+ installed_modules := get_installed_modules()
+ joined := search_keys.join(', ')
+ mut index := 0
+ for mod in modules {
+ // TODO for some reason .filter results in substr error, so do it manually
+ for k in search_keys {
+ if !mod.contains(k) {
+ continue
+ }
+ if index == 0 {
+ println('Search results for "$joined":\n')
+ }
+ index++
+ mut parts := mod.split('.')
+ // in case the author isn't present
+ if parts.len == 1 {
+ parts << parts[0]
+ parts[0] = ' '
+ } else {
+ parts[0] = ' by ${parts[0]} '
+ }
+ installed := if mod in installed_modules { ' (installed)' } else { '' }
+ println('${index}. ${parts[1]}${parts[0]}[$mod]$installed')
+ break
+ }
+ }
+ if index == 0 {
+ vexe := os.getenv('VEXE')
+ vroot := os.real_path(os.dir(vexe))
+ mut messages := ['No module(s) found for `$joined` .']
+ for vlibmod in search_keys {
+ if os.is_dir(os.join_path(vroot, 'vlib', vlibmod)) {
+ messages << 'There is already an existing "$vlibmod" module in vlib, so you can just `import $vlibmod` .'
+ }
+ }
+ for m in messages {
+ println(m)
+ }
+ } else {
+ println('\nUse "v install author_name.module_name" to install the module.')
+ }
+}
+
+fn vpm_install(module_names []string) {
+ if settings.is_help {
+ vhelp.show_topic('install')
+ exit(0)
+ }
+ if module_names.len == 0 {
+ println('´v install´ requires *at least one* module name.')
+ exit(2)
+ }
+ mut errors := 0
+ for n in module_names {
+ name := n.trim_space().replace('_', '-')
+ mod := get_module_meta_info(name) or {
+ errors++
+ println('Errors while retrieving meta data for module $name:')
+ println(err)
+ continue
+ }
+ mut vcs := mod.vcs
+ if vcs == '' {
+ vcs = supported_vcs_systems[0]
+ }
+ if vcs !in supported_vcs_systems {
+ errors++
+ println('Skipping module "$name", since it uses an unsupported VCS {$vcs} .')
+ continue
+ }
+ mod_name_as_path := mod.name.replace('.', os.path_separator).replace('-', '_').to_lower()
+ final_module_path := os.real_path(os.join_path(settings.vmodules_path, mod_name_as_path))
+ if os.exists(final_module_path) {
+ vpm_update([name])
+ continue
+ }
+ println('Installing module "$name" from $mod.url to $final_module_path ...')
+ vcs_install_cmd := supported_vcs_install_cmds[vcs]
+ cmd := '$vcs_install_cmd "$mod.url" "$final_module_path"'
+ verbose_println(' command: $cmd')
+ cmdres := os.execute(cmd)
+ if cmdres.exit_code != 0 {
+ errors++
+ println('Failed installing module "$name" to "$final_module_path" .')
+ verbose_println('Failed command: $cmd')
+ verbose_println('Failed command output:\n$cmdres.output')
+ continue
+ }
+ resolve_dependencies(name, final_module_path, module_names)
+ }
+ if errors > 0 {
+ exit(1)
+ }
+}
+
+fn vpm_update(m []string) {
+ mut module_names := m.clone()
+ if settings.is_help {
+ vhelp.show_topic('update')
+ exit(0)
+ }
+ if module_names.len == 0 {
+ module_names = get_installed_modules()
+ }
+ mut errors := 0
+ for name in module_names {
+ final_module_path := valid_final_path_of_existing_module(name) or { continue }
+ os.chdir(final_module_path)
+ println('Updating module "$name"...')
+ verbose_println(' work folder: $final_module_path')
+ vcs := vcs_used_in_dir(final_module_path) or { continue }
+ vcs_cmd := supported_vcs_update_cmds[vcs[0]]
+ verbose_println(' command: $vcs_cmd')
+ vcs_res := os.execute('$vcs_cmd')
+ if vcs_res.exit_code != 0 {
+ errors++
+ println('Failed updating module "$name".')
+ verbose_println('Failed command: $vcs_cmd')
+ verbose_println('Failed details:\n$vcs_res.output')
+ continue
+ } else {
+ verbose_println(' $vcs_res.output.trim_space()')
+ }
+ resolve_dependencies(name, final_module_path, module_names)
+ }
+ if errors > 0 {
+ exit(1)
+ }
+}
+
+fn get_outdated() ?[]string {
+ module_names := get_installed_modules()
+ mut outdated := []string{}
+ for name in module_names {
+ final_module_path := valid_final_path_of_existing_module(name) or { continue }
+ os.chdir(final_module_path)
+ vcs := vcs_used_in_dir(final_module_path) or { continue }
+ vcs_cmd_steps := supported_vcs_outdated_steps[vcs[0]]
+ mut outputs := []string{}
+ for step in vcs_cmd_steps {
+ res := os.execute(step)
+ if res.exit_code < 0 {
+ verbose_println('Error command: $step')
+ verbose_println('Error details:\n$res.output')
+ return error('Error while checking latest commits for "$name".')
+ }
+ if vcs[0] == 'hg' {
+ if res.exit_code == 1 {
+ outdated << name
+ }
+ } else {
+ outputs << res.output
+ }
+ }
+ if vcs[0] == 'git' && outputs[1] != outputs[2] {
+ outdated << name
+ }
+ }
+ return outdated
+}
+
+fn vpm_upgrade() {
+ outdated := get_outdated() or { exit(1) }
+ if outdated.len > 0 {
+ vpm_update(outdated)
+ } else {
+ println('Modules are up to date.')
+ }
+}
+
+fn vpm_outdated() {
+ outdated := get_outdated() or { exit(1) }
+ if outdated.len > 0 {
+ println('Outdated modules:')
+ for m in outdated {
+ println(' $m')
+ }
+ } else {
+ println('Modules are up to date.')
+ }
+}
+
+fn vpm_list() {
+ module_names := get_installed_modules()
+ if module_names.len == 0 {
+ println('You have no modules installed.')
+ exit(0)
+ }
+ println('Installed modules:')
+ for mod in module_names {
+ println(' $mod')
+ }
+}
+
+fn vpm_remove(module_names []string) {
+ if settings.is_help {
+ vhelp.show_topic('remove')
+ exit(0)
+ }
+ if module_names.len == 0 {
+ println('´v remove´ requires *at least one* module name.')
+ exit(2)
+ }
+ for name in module_names {
+ final_module_path := valid_final_path_of_existing_module(name) or { continue }
+ println('Removing module "$name"...')
+ verbose_println('removing folder $final_module_path')
+ os.rmdir_all(final_module_path) or {
+ verbose_println('error while removing "$final_module_path": $err.msg')
+ }
+ // delete author directory if it is empty
+ author := name.split('.')[0]
+ author_dir := os.real_path(os.join_path(settings.vmodules_path, author))
+ if !os.exists(author_dir) {
+ continue
+ }
+ if os.is_dir_empty(author_dir) {
+ verbose_println('removing author folder $author_dir')
+ os.rmdir(author_dir) or {
+ verbose_println('error while removing "$author_dir": $err.msg')
+ }
+ }
+ }
+}
+
+fn valid_final_path_of_existing_module(name string) ?string {
+ mod_name_as_path := name.replace('.', os.path_separator).replace('-', '_').to_lower()
+ name_of_vmodules_folder := os.join_path(settings.vmodules_path, mod_name_as_path)
+ final_module_path := os.real_path(name_of_vmodules_folder)
+ if !os.exists(final_module_path) {
+ println('No module with name "$name" exists at $name_of_vmodules_folder')
+ return none
+ }
+ if !os.is_dir(final_module_path) {
+ println('Skipping "$name_of_vmodules_folder", since it is not a folder.')
+ return none
+ }
+ vcs_used_in_dir(final_module_path) or {
+ println('Skipping "$name_of_vmodules_folder", since it does not use a supported vcs.')
+ return none
+ }
+ return final_module_path
+}
+
+fn ensure_vmodules_dir_exist() {
+ if !os.is_dir(settings.vmodules_path) {
+ println('Creating $settings.vmodules_path/ ...')
+ os.mkdir(settings.vmodules_path) or { panic(err) }
+ }
+}
+
+fn vpm_help() {
+ vhelp.show_topic('vpm')
+}
+
+fn vcs_used_in_dir(dir string) ?[]string {
+ mut vcs := []string{}
+ for repo_subfolder in supported_vcs_folders {
+ checked_folder := os.real_path(os.join_path(dir, repo_subfolder))
+ if os.is_dir(checked_folder) {
+ vcs << repo_subfolder.replace('.', '')
+ }
+ }
+ if vcs.len == 0 {
+ return none
+ }
+ return vcs
+}
+
+fn get_installed_modules() []string {
+ dirs := os.ls(settings.vmodules_path) or { return [] }
+ mut modules := []string{}
+ for dir in dirs {
+ adir := os.join_path(settings.vmodules_path, dir)
+ if dir in excluded_dirs || !os.is_dir(adir) {
+ continue
+ }
+ if os.exists(os.join_path(adir, 'v.mod')) && os.exists(os.join_path(adir, '.git', 'config')) {
+ // an official vlang module with a short module name, like `vsl`, `ui` or `markdown`
+ modules << dir
+ continue
+ }
+ author := dir
+ mods := os.ls(adir) or { continue }
+ for m in mods {
+ vcs_used_in_dir(os.join_path(adir, m)) or { continue }
+ modules << '${author}.$m'
+ }
+ }
+ return modules
+}
+
+fn get_all_modules() []string {
+ url := get_working_server_url()
+ r := http.get(url) or { panic(err) }
+ if r.status_code != 200 {
+ println('Failed to search vpm.vlang.io. Status code: $r.status_code')
+ exit(1)
+ }
+ s := r.text
+ mut read_len := 0
+ mut modules := []string{}
+ for read_len < s.len {
+ mut start_token := '<a href="/mod'
+ end_token := '</a>'
+ // get the start index of the module entry
+ mut start_index := s.index_after(start_token, read_len)
+ if start_index == -1 {
+ break
+ }
+ // get the index of the end of anchor (a) opening tag
+ // we use the previous start_index to make sure we are getting a module and not just a random 'a' tag
+ start_token = '">'
+ start_index = s.index_after(start_token, start_index) + start_token.len
+ // get the index of the end of module entry
+ end_index := s.index_after(end_token, start_index)
+ if end_index == -1 {
+ break
+ }
+ modules << s[start_index..end_index]
+ read_len = end_index
+ if read_len >= s.len {
+ break
+ }
+ }
+ return modules
+}
+
+fn resolve_dependencies(name string, module_path string, module_names []string) {
+ vmod_path := os.join_path(module_path, 'v.mod')
+ if !os.exists(vmod_path) {
+ return
+ }
+ data := os.read_file(vmod_path) or { return }
+ vmod := parse_vmod(data)
+ mut deps := []string{}
+ // filter out dependencies that were already specified by the user
+ for d in vmod.deps {
+ if d !in module_names {
+ deps << d
+ }
+ }
+ if deps.len > 0 {
+ println('Resolving $deps.len dependencies for module "$name"...')
+ verbose_println('Found dependencies: $deps')
+ vpm_install(deps)
+ }
+}
+
+fn parse_vmod(data string) Vmod {
+ keys := ['name', 'version', 'deps']
+ mut m := map{
+ 'name': ''
+ 'version': ''
+ 'deps': ''
+ }
+ for key in keys {
+ mut key_index := data.index('$key:') or { continue }
+ key_index += key.len + 1
+ m[key] = data[key_index..data.index_after('\n', key_index)].trim_space().replace("'",
+ '').replace('[', '').replace(']', '')
+ }
+ mut vmod := Vmod{}
+ vmod.name = m['name']
+ vmod.version = m['version']
+ if m['deps'].len > 0 {
+ vmod.deps = m['deps'].split(',')
+ }
+ return vmod
+}
+
+fn get_working_server_url() string {
+ server_urls := if settings.server_urls.len > 0 {
+ settings.server_urls
+ } else {
+ default_vpm_server_urls
+ }
+ for url in server_urls {
+ verbose_println('Trying server url: $url')
+ http.head(url) or {
+ verbose_println(' $url failed.')
+ continue
+ }
+ return url
+ }
+ panic('No responding vpm server found. Please check your network connectivity and try again later.')
+}
+
+// settings context:
+struct VpmSettings {
+mut:
+ is_help bool
+ is_verbose bool
+ server_urls []string
+ vmodules_path string
+}
+
+const (
+ settings = &VpmSettings{}
+)
+
+fn init_settings() {
+ mut s := &VpmSettings(0)
+ unsafe {
+ s = settings
+ }
+ s.is_help = '-h' in os.args || '--help' in os.args || 'help' in os.args
+ s.is_verbose = '-v' in os.args
+ s.server_urls = cmdline.options(os.args, '-server-url')
+ s.vmodules_path = os.vmodules_dir()
+}
+
+fn verbose_println(s string) {
+ if settings.is_verbose {
+ println(s)
+ }
+}
+
+fn get_module_meta_info(name string) ?Mod {
+ mut errors := []string{}
+ for server_url in default_vpm_server_urls {
+ modurl := server_url + '/jsmod/$name'
+ verbose_println('Retrieving module metadata from: $modurl ...')
+ r := http.get(modurl) or {
+ errors << 'Http server did not respond to our request for ${modurl}.'
+ errors << 'Error details: $err'
+ continue
+ }
+ if r.status_code == 404 || r.text.trim_space() == '404' {
+ errors << 'Skipping module "$name", since $server_url reported that "$name" does not exist.'
+ continue
+ }
+ if r.status_code != 200 {
+ errors << 'Skipping module "$name", since $server_url responded with $r.status_code http status code. Please try again later.'
+ continue
+ }
+ s := r.text
+ if s.len > 0 && s[0] != `{` {
+ errors << 'Invalid json data'
+ errors << s.trim_space().limit(100) + '...'
+ continue
+ }
+ mod := json.decode(Mod, s) or {
+ errors << 'Skipping module "$name", since its information is not in json format.'
+ continue
+ }
+ if '' == mod.url || '' == mod.name {
+ errors << 'Skipping module "$name", since it is missing name or url information.'
+ continue
+ }
+ return mod
+ }
+ return error(errors.join_lines())
+}
+
+fn vpm_show(module_names []string) {
+ installed_modules := get_installed_modules()
+ for module_name in module_names {
+ if module_name !in installed_modules {
+ module_meta_info := get_module_meta_info(module_name) or { continue }
+ print('
+Name: $module_meta_info.name
+Homepage: $module_meta_info.url
+Downloads: $module_meta_info.nr_downloads
+Installed: False
+--------
+')
+ continue
+ }
+ path := os.join_path(os.vmodules_dir(), module_name)
+ mod := vmod.from_file(os.join_path(path, 'v.mod')) or { continue }
+ print('Name: $mod.name
+Version: $mod.version
+Description: $mod.description
+Homepage: $mod.repo_url
+Author: $mod.author
+License: $mod.license
+Location: $path
+Requires: ${mod.dependencies.join(', ')}
+--------
+')
+ }
+}