# frozen_string_literal: true module LicenseFinder class GoModules FORMAT = "'{{.Main}},{{.Path}},{{.Version}},{{.Dir}}'" HEADER = [:main_module, :name, :version, :dir].freeze def prepare return if vendored? within_project_path do tool_box.install(tool: :golang) shell.execute([:go, :mod, :download, '-json'], capture: false) end end def current_packages within_project_path do modules = vendored? ? parse_go_sum : go_list_all modules.map { |hash| map_from(hash) }.compact end end private def go_list_all env = { 'GOFLAGS' => ENV.fetch('GOFLAGS', '-mod=readonly') } command = [:go, :list, '-m', '-f', FORMAT, :all] stdout, _stderr, status = shell.execute(command, env: env) return [] unless status.success? stdout.each_line.map { |line| Hash[HEADER.zip(line.chomp.split(','))] } end def parse_go_sum go_sum_path .each_line.map { |x| x.split(' ') } .each_with_object({}) do |(name, version), memo| next unless module_path?(name) memo["#{name}:#{version}"] = { name: name, version: version.split('/')[0], dir: vendored_path_to(name) } end.values end def map_from(hash) return if hash[:main_module] == "true" Dependency.new( 'Go', hash[:name], hash[:version], install_path: install_dir_for(hash), detection_path: go_sum_path ) end def go_sum_path @go_sum_path ||= Pathname.glob(project_path.join('go.sum')).find(&:exist?) end def vendor_path @vendor_path ||= go_sum_path.parent.join('vendor') end def vendored? vendor_path.exist? && vendor_path.directory? end def vendored_path_to(module_name) vendor_path.join(module_name) end def install_dir_for(hash) dir = hash[:dir] pathname = dir && !dir.empty? ? Pathname.new(dir) : vendored_path_to(hash[:name]) pathname.exist? ? pathname : nil end # https://golang.org/ref/mod#tmp_9 def module_path?(module_path) !module_path.start_with?('/') && !module_path.end_with?('/') && module_path.split('/').all? { |x| element?(x) } end def element?(element) !element.empty? && !element.start_with?('.') && !element.end_with?('.') && element.match?(/\A[A-Za-z0-9+-._~]+\Z/) end end end