module main import os import net.http import net.urllib import sync.pool import v.vmod import json import term import log struct ModuleVpmInfo { // id int name string url string vcs string nr_downloads int } pub struct ModuleDateInfo { name string mut: outdated bool exec_err bool } @[params] struct ErrorOptions { details string verbose bool // is used to only output the error message if the verbose setting is enabled. } const vexe = os.quoted_path(os.getenv('VEXE')) const home_dir = os.home_dir() fn get_mod_date_info(mut pp pool.PoolProcessor, idx int, wid int) &ModuleDateInfo { mut result := &ModuleDateInfo{ name: pp.get_item[string](idx) } path := get_path_of_existing_module(result.name) or { return result } vcs := vcs_used_in_dir(path) or { return result } is_hg := vcs.cmd == 'hg' mut outputs := []string{} for step in vcs.args.outdated { cmd := '${vcs.cmd} ${vcs.args.path} "${path}" ${step}' res := os.execute(cmd) if res.exit_code < 0 { verbose_println('Error command: ${cmd}') verbose_println('Error details:\n${res.output}') result.exec_err = true return result } if is_hg && res.exit_code == 1 { result.outdated = true return result } outputs << res.output } // vcs.cmd == 'git' if !is_hg && outputs[1] != outputs[2] { result.outdated = true } return result } fn get_mod_vpm_info(name string) !ModuleVpmInfo { if name.len < 2 || (!name[0].is_digit() && !name[0].is_letter()) { return error('invalid module name `${name}`.') } mut errors := []string{} for url in vpm_server_urls { modurl := url + '/api/packages/${name}' verbose_println('Retrieving metadata for `${name}` 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.body.trim_space() == '404' { errors << 'Skipping module `${name}`, since `${url}` reported that `${name}` does not exist.' continue } if r.status_code != 200 { errors << 'Skipping module `${name}`, since `${url}` responded with ${r.status_code} http status code. Please try again later.' continue } s := r.body if s.len > 0 && s[0] != `{` { errors << 'Invalid json data' errors << s.trim_space().limit(100) + '...' continue } mod := json.decode(ModuleVpmInfo, 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 } vpm_log(@FILE_LINE, @FN, 'name: ${name}; mod: ${mod}') return mod } return error(errors.join_lines()) } fn get_ident_from_url(raw_url string) !(string, string) { url := urllib.parse(raw_url) or { return error('failed to parse module URL `${raw_url}`.') } publisher, mut name := url.path.trim_left('/').rsplit_once('/') or { return error('failed to retrieve module name for `${url}`.') } vpm_log(@FILE_LINE, @FN, 'raw_url: ${raw_url}; publisher: ${publisher}; name: ${name}') name = if name.ends_with('.git') { name.replace('.git', '') } else { name } return publisher, name } fn get_name_from_url(raw_url string) !string { _, name := get_ident_from_url(raw_url)! return name } fn normalize_mod_path(path string) string { return path.replace('-', '_').to_lower() } fn get_outdated() ![]string { installed := get_installed_modules() mut outdated := []string{} mut pp := pool.new_pool_processor(callback: get_mod_date_info) pp.work_on_items(installed) for res in pp.get_results[ModuleDateInfo]() { if res.exec_err { return error('failed to check the latest commits for `${res.name}`.') } if res.outdated { outdated << res.name } } return outdated } fn get_all_modules() []string { url := get_working_server_url() r := http.get(url) or { vpm_error(err.msg(), verbose: true) exit(1) } if r.status_code != 200 { vpm_error('failed to search vpm.vlang.io.', details: 'Status code: ${r.status_code}') exit(1) } s := r.body mut read_len := 0 mut modules := []string{} for read_len < s.len { mut start_token := "' // get the start index of the module entry mut start_index := s.index_after(start_token, read_len) if start_index == -1 { start_token = '