github.com/replit/upm@v0.0.0-20240423230255-9ce4fc3ea24c/resources/ruby/guess-gems.rb (about)

     1  require 'parser/current'
     2  require 'json'
     3  
     4  # For now, we will only guess gems that come preinstalled in Polygott.
     5  # The mapping between what you require and the gem name is not always 1-to-1,
     6  # so we will need some sort of mapping like what we do with PyPi if we want
     7  # to do complete ruby gem guessing.
     8  $allowed_gems = {
     9    "sinatra" => "sinatra",
    10    "sinatra/base" => "sinatra",
    11    "stripe" => "stripe"
    12  }
    13  
    14  def guess_gems(code, guesses)
    15    root = Parser::CurrentRuby.parse(code)
    16    traverse_node(root, guesses)
    17  end
    18  
    19  def traverse_node(node, guesses)
    20    if node.class != Parser::AST::Node
    21      return
    22    end
    23  
    24    # Looking for any s(:send, nil, :require, s(:str, "gem"))
    25    if node.type == :send && node.children[1] == :require
    26      req_node = node.children[2]
    27      if req_node.class == Parser::AST::Node && req_node.type == :str
    28        process_require(req_node.children.first, guesses)
    29        return
    30      end
    31    end
    32  
    33    node.children.each { |child|
    34      traverse_node(child, guesses)
    35    }
    36  end
    37  
    38  def process_require(req_str, guesses)
    39    if req_str.empty?
    40      return
    41    end
    42  
    43    # Skip absolute or relative requires
    44    if req_str.start_with?('/') || req_str.start_with?('.')
    45      return
    46    end
    47  
    48    gem = $allowed_gems[req_str]
    49    if gem
    50      guesses[gem] = [gem]
    51    end
    52  end
    53  
    54  guesses = {}
    55  
    56  Dir.glob("**/*.rb").reject {|f| f['./.bundle'] }.each { |file|
    57    guess_gems(File.read(file), guesses)
    58  }
    59  
    60  puts guesses.to_json()