Module: Archsight::Import::Handlers::GoModuleParser

Included in:
GoDepResolver, GoGrapher
Defined in:
lib/archsight/import/handlers/go_module_parser.rb

Overview

Shared Go module parsing utilities included by GoGrapher and GoDepResolver.

Provides go.mod discovery, module name reading, require parsing, and the component_name convention (strip SCM host, join path segments with ":").

Instance Method Summary collapse

Instance Method Details

#component_name(mod_name) ⇒ Object

Convert a Go module path to an ApplicationComponent name. Strips the SCM host segment and joins remaining path segments with ":". "github.com/ionos-cloud/event-gateway/pkg" → "ionos-cloud:event-gateway:pkg"



106
107
108
109
110
# File 'lib/archsight/import/handlers/go_module_parser.rb', line 106

def component_name(mod_name)
  parts = mod_name.split("/")
  parts.shift
  parts.join(":")
end

#discover_modules(repo_root) ⇒ Array<Array<String>>

Discover all Go modules in a repository root. Handles go.work workspaces, single-module repos, and multi-module monorepos (root go.mod plus subdirectory go.mod files without go.work).

Parameters:

  • repo_root (String)

    Absolute path to the repository root

Returns:

  • (Array<Array<String>>)

    List of [rel_dir, mod_name] pairs



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/archsight/import/handlers/go_module_parser.rb', line 16

def discover_modules(repo_root)
  work_file = File.join(repo_root, "go.work")
  modules = []

  if File.exist?(work_file)
    content = File.read(work_file)
    dirs = if (block_match = content.match(/\buse\s*\((.*?)\)/m))
             block_match[1].split.map(&:strip).reject(&:empty?)
           else
             content.scan(/\buse\s+(\S+)/).flatten
           end
    dirs.each do |d|
      rel = d == "." ? "" : d.delete_prefix("./")
      abs_dir = rel.empty? ? repo_root : File.join(repo_root, rel)
      mod_name = read_module_name(abs_dir)
      modules << [rel.empty? ? "." : rel, mod_name] if mod_name
    end
  else
    root_mod = read_module_name(repo_root)
    modules << [".", root_mod] if root_mod

    # Also scan subdirectories for additional go.mod files (multi-module monorepo without go.work)
    Find.find(repo_root) do |path|
      bn = File.basename(path)
      Find.prune if File.directory?(path) && %w[vendor testdata .git node_modules].include?(bn)
      next unless bn == "go.mod"

      mod_dir = File.dirname(path)
      next if mod_dir == repo_root # already added above

      rel = mod_dir.delete_prefix("#{repo_root}/")
      name = read_module_name(mod_dir)
      modules << [rel, name] if name
    end
  end

  modules
end

#go_mod_requires(mod_dir) ⇒ Array<String>

Parse require directives from go.mod, handling both block and single-line forms.

Returns:

  • (Array<String>)

    Unique module paths listed in require directives



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/archsight/import/handlers/go_module_parser.rb', line 70

def go_mod_requires(mod_dir)
  gomod = File.join(mod_dir, "go.mod")
  return [] unless File.exist?(gomod)

  content = File.read(gomod)
  paths = []

  # Block form: require ( ... )
  content.scan(/\brequire\s*\(([^)]*)\)/m) do |block|
    block[0].each_line do |line|
      stripped = line.strip.split("//").first&.strip
      mod_path = stripped&.split&.first
      paths << mod_path if mod_path && !mod_path.empty?
    end
  end

  # Single-line form: require module/path vX.Y.Z (strip block forms first to avoid double-counting)
  content.gsub(/\brequire\s*\([^)]*\)/m, "").scan(/^\s*require\s+(\S+)/) do |m|
    paths << m[0]
  end

  paths.uniq
end

#read_module_name(mod_dir) ⇒ String?

Read the module name declared in go.mod.

Returns:

  • (String, nil)

    Module path or nil if go.mod absent / no module line



57
58
59
60
61
62
63
64
65
66
# File 'lib/archsight/import/handlers/go_module_parser.rb', line 57

def read_module_name(mod_dir)
  gomod = File.join(mod_dir, "go.mod")
  return nil unless File.exist?(gomod)

  File.foreach(gomod) do |line|
    m = line.match(/^\s*module\s+(\S+)/)
    return m[1] if m
  end
  nil
end

#same_origin_prefix(mod_name) ⇒ String?

Return the SCM host+org prefix shared by modules in the same org, e.g. "github.com/ionos-cloud/".

Returns:

  • (String, nil)

    Prefix with trailing slash, or nil for single-segment names



96
97
98
99
100
101
# File 'lib/archsight/import/handlers/go_module_parser.rb', line 96

def same_origin_prefix(mod_name)
  parts = mod_name.split("/")
  return nil if parts.length < 2

  "#{parts[0, 2].join("/")}/"
end