~tomleb/repo-url.nvim

aa5766dff5488ace0d895613db9fc3c1473b22cd — Tom Lebreux 6 months ago 95c50b5
A bit of refactoring and a bug fix
1 files changed, 104 insertions(+), 53 deletions(-)

M lua/repo-url/init.lua
M lua/repo-url/init.lua => lua/repo-url/init.lua +104 -53
@@ 159,17 159,60 @@ local pick_urls = function(hostname)
  return config.urls_by_hostname[hostname]
end

local function go_modcache_info(filename)
  local gocache = vim.fn.systemlist({ 'go', 'env', 'GOMODCACHE' })[1]
  if not gocache or filename:sub(1, #gocache) ~= gocache then
    return false
  end

  local trimmed = filename:sub(#gocache + 2) -- Remove the prefix (and /)
  local module = trimmed:match '^(.-@.-)/'
  local rel_filepath = trimmed:match '.-@.-/(.*)'

  local output = vim.fn.system { 'go', 'mod', 'download', '-json', module }
local extract_module_version = function(path)
  local cache = vim.fn.systemlist({ 'go', 'env', 'GOMODCACHE' })[1]
  if not cache or path:sub(1, #cache) ~= cache then
    error 'path not in cache'
  end

  local trimmed = path:sub(#cache + 2) -- Remove the prefix (and /)
  local match, _, module, version = string.find(trimmed, '([^@]+)@([^/]+)')
  if not match then
    error 'not matched'
  end
  local rel_filepath = trimmed:match '.-@.-/(.*)' or ''
  return module, version, rel_filepath
end

local extract_module_version_test = function()
  local cache = vim.fn.systemlist({ 'go', 'env', 'GOMODCACHE' })[1]
  local module, version, file

  module, version, file = extract_module_version(cache .. '/github.com/google/uuid@v1.3.1')
  assert(module == 'github.com/google/uuid')
  assert(version == 'v1.3.1')
  assert(file == '')

  module, version, file = extract_module_version(cache .. '/github.com/google/uuid@v1.3.1/')
  assert(module == 'github.com/google/uuid')
  assert(version == 'v1.3.1')
  assert(file == '')

  module, version, file = extract_module_version(cache .. '/github.com/google/groupcache@v0.0.0-20210331224755-41bb18bfe9da/')
  assert(module == 'github.com/google/groupcache')
  assert(version == 'v0.0.0-20210331224755-41bb18bfe9da')
  assert(file == '')

  module, version, file =
    extract_module_version(cache .. '/github.com/rancher/rancher/pkg/apis@v0.0.0-20211022200336-bd75b096ba9e/management.cattle.io/v3/zz_generated_deepcopy.go')
  assert(module == 'github.com/rancher/rancher/pkg/apis')
  assert(version == 'v0.0.0-20211022200336-bd75b096ba9e')
  assert(file == 'management.cattle.io/v3/zz_generated_deepcopy.go')
end

--
--
--  "Origin": { -> Always present if using GOPROXY=direct
--    "VCS": "git",
--    "URL": "https://github.com/rancher/rancher",
--    "Subdir": "pkg/apis", -> When it's a module NOT at the top level of the repo
--    "Hash": "bd75b096ba9e9736c82faadd7f08a4c73f6cdd2a",  -> The commit
--    "Ref": "/refs/tags/v0.1.2" -> not always there
--   }
--
--
local function go_modcache_info(module, version, rel_filepath)
  local output = vim.fn.system { 'go', 'mod', 'download', '-json', string.format('%s@%s', module, version) }
  if output == '' then
    return false
  end


@@ 182,6 225,7 @@ local function go_modcache_info(filename)
  if not json.Origin then
    -- XXX: This will be super slow in most cases. Need to find a better way and
    -- potentially make everything async..
    vim.notify('Fallback to slow gomod download', vim.log.levels.WARN)
    output = vim.fn.system { 'env', 'GOPROXY=direct', 'GOMODCACHE=' .. fallback_gocache_dir, 'go', 'mod', 'download', '-json', module }
    if output == '' then
      return false


@@ 205,6 249,12 @@ local function go_modcache_info(filename)
    end
  end

  if json.Origin.Subdir ~= nil then
    -- rel_filepath is always relative to the module, so if the module is not
    -- at the root of the repository, we must append the Subdir to it
    rel_filepath = string.format('%s/%s', json.Origin.Subdir, rel_filepath)
  end

  local hostname = json.Origin.URL:match '://([^/]+)/'
  return {
    commit = commit,


@@ 351,17 401,20 @@ local get_repo_info = function()
    line2 = vim.fn.getpos('.')[2]
  end

  local gomodcache = go_modcache_info(filename)
  if gomodcache then
    local hostname = gomodcache.hostname
    local info = {
      base_url = gomodcache.remote_url,
      ref = gomodcache.commit,
      filepath = gomodcache.rel_filepath,
      line1 = line1 or nil,
      line2 = line2 or nil,
    }
    return { hostname = hostname, info = info }
  local ok, module, version, filepath_in_module = pcall(extract_module_version, filename)
  if ok then
    local gomodcache = go_modcache_info(module, version, filepath_in_module)
    if gomodcache then
      local hostname = gomodcache.hostname
      local info = {
        base_url = gomodcache.remote_url,
        ref = gomodcache.commit,
        filepath = gomodcache.rel_filepath,
        line1 = line1 or nil,
        line2 = line2 or nil,
      }
      return { hostname = hostname, info = info }
    end
  end

  local project_dir = M.find_git_project_root(vim.fn.fnamemodify(filename, ':h'))


@@ 532,66 585,64 @@ M.open_tree_url = function()
  open_url(url)
end

local get_gomod_repo_and_version = function(mline)
local get_gomod_module_version = function(mline)
  local line = mline or vim.fn.getline '.'
  local match, repo, version
  local match, module, version

  -- Matches
  -- 	k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230215170314-b923b89432b0
  match, _, repo, version = string.find(line, '^	[^ ]+ => ([^ ]+) ([^ ]+)')
  match, _, module, version = string.find(line, '^	[^ ]+ => ([^ ]+) ([^ ]+)')
  if match then
    return repo, version
    return module, version
  end

  -- Matches
  -- 	k8s.io/apimachinery v1.1.1 => k8s.io/apimachinery v0.0.0-20230215170314-b923b89432b0
  match, _, repo, version = string.find(line, '^	[^ ]+ [^ ]+ => ([^ ]+) ([^ ]+)')
  match, _, module, version = string.find(line, '^	[^ ]+ [^ ]+ => ([^ ]+) ([^ ]+)')
  if match then
    return repo, version
    return module, version
  end

  -- Matches
  -- replace k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230215170314-b923b89432b0
  match, _, repo, version = string.find(line, '^replace [^ ]+ => ([^ ]+) ([^ ]+)')
  match, _, module, version = string.find(line, '^replace [^ ]+ => ([^ ]+) ([^ ]+)')
  if match then
    return repo, version
    return module, version
  end

  -- Matches
  -- replace k8s.io/apimachinery v1.1.1 => k8s.io/apimachinery v0.0.0-20230215170314-b923b89432b0
  match, _, repo, version = string.find(line, '^replace [^ ]+ [^ ]+ => ([^ ]+) ([^ ]+)')
  match, _, module, version = string.find(line, '^replace [^ ]+ [^ ]+ => ([^ ]+) ([^ ]+)')
  if match then
    return repo, version
    return module, version
  end

  --  Matches
  --  	k8s.io/apimachinery v3.0.1
  -- 	k8s.io/apimachinery v1.1.1 // indirect
  match, _, repo, version = string.find(line, '^	([^ ]+) ([^ ]+)')
  match, _, module, version = string.find(line, '^	([^ ]+) ([^ ]+)')
  if match then
    return repo, version
    return module, version
  end

  --  Matches
  --  require k8s.io/apimachinery v3.0.1
  --  require k8s.io/apimachinery v1.1.1 // indirect
  match, _, repo, version = string.find(line, '^require ([^ ]+) ([^ ]+)')
  match, _, module, version = string.find(line, '^require ([^ ]+) ([^ ]+)')
  if match then
    return repo, version
    return module, version
  end

  return nil, nil
end

M.get_gomod_tree_url = function()
  local repo, version = get_gomod_repo_and_version()
  if not repo then
  local module, version = get_gomod_module_version()
  if not module then
    error 'unsupported go.mod directive'
  end

  local gocache = vim.fn.systemlist({ 'go', 'env', 'GOMODCACHE' })[1]
  local dir = string.format('%s/%s@%s/', gocache, repo, version)
  local gomodcache = go_modcache_info(dir)
  local gomodcache = go_modcache_info(module, version, nil)
  if gomodcache then
    local hostname = gomodcache.hostname
    local info = {


@@ 629,27 680,25 @@ end

local get_gosum_repo_and_version = function()
  local line = vim.fn.getline '.'
  local match, repo, version
  local match, module, version

  -- Matches
  -- cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
  match, _, repo, version = string.find(line, '^([^ ]+) ([^ /]+)')
  match, _, module, version = string.find(line, '^([^ ]+) ([^ /]+)')
  if match then
    return repo, version
    return module, version
  end

  return nil, nil
end

M.get_gosum_tree_url = function()
  local repo, version = get_gosum_repo_and_version()
  if not repo then
  local module, version = get_gosum_repo_and_version()
  if not module then
    error 'unsupported go.sum directive'
  end

  local gocache = vim.fn.systemlist({ 'go', 'env', 'GOMODCACHE' })[1]
  local dir = string.format('%s/%s@%s/', gocache, repo, version)
  local gomodcache = go_modcache_info(dir)
  local gomodcache = go_modcache_info(module, version)
  if gomodcache then
    local hostname = gomodcache.hostname
    local info = {


@@ 688,19 737,19 @@ end
local get_gomod_repo_and_version_test = function()
  local repo, version

  repo, version = get_gomod_repo_and_version '	k8s.io/api v0.27.4'
  repo, version = get_gomod_module_version '	k8s.io/api v0.27.4'
  assert(repo == 'k8s.io/api')
  assert(version == 'v0.27.4')

  repo, version = get_gomod_repo_and_version 'require k8s.io/api v0.27.4'
  repo, version = get_gomod_module_version 'require k8s.io/api v0.27.4'
  assert(repo == 'k8s.io/api')
  assert(version == 'v0.27.4')

  repo, version = get_gomod_repo_and_version '	k8s.io/api => k8s.io/api v0.27.4'
  repo, version = get_gomod_module_version '	k8s.io/api => k8s.io/api v0.27.4'
  assert(repo == 'k8s.io/api')
  assert(version == 'v0.27.4')

  repo, version = get_gomod_repo_and_version '	k8s.io/api v0.26.3 => k8s.io/api v0.27.4'
  repo, version = get_gomod_module_version '	k8s.io/api v0.26.3 => k8s.io/api v0.27.4'
  assert(repo == 'k8s.io/api')
  assert(version == 'v0.27.4')
end


@@ 712,7 761,9 @@ M.setup = function(uconfig)
  vim.fn.mkdir(fallback_gocache_dir, 'p')

  -- XXX: Need to learn how to run unit test with neotest instead of here..
  extract_module_version_test()
  get_gomod_repo_and_version_test()

  vim.validate { config = { uconfig, 'table', true } }
  config = vim.tbl_deep_extend('force', config, uconfig)