cutting_edge 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/Gemfile +10 -0
 - data/LICENSE +674 -0
 - data/README.md +95 -0
 - data/Rakefile +159 -0
 - data/bin/cutting_edge +72 -0
 - data/cutting_edge.gemspec +57 -0
 - data/lib/cutting_edge.rb +3 -0
 - data/lib/cutting_edge/app.rb +77 -0
 - data/lib/cutting_edge/badge.rb +46 -0
 - data/lib/cutting_edge/langs.rb +123 -0
 - data/lib/cutting_edge/langs/python.rb +102 -0
 - data/lib/cutting_edge/langs/ruby.rb +47 -0
 - data/lib/cutting_edge/langs/rust.rb +68 -0
 - data/lib/cutting_edge/repo.rb +67 -0
 - data/lib/cutting_edge/versions.rb +46 -0
 - data/lib/cutting_edge/workers/badge.rb +22 -0
 - data/lib/cutting_edge/workers/dependency.rb +100 -0
 - data/lib/cutting_edge/workers/helpers.rb +24 -0
 - data/spec/langs/python_spec.rb +89 -0
 - data/spec/langs/rust_spec.rb +81 -0
 - data/spec/spec_helper.rb +111 -0
 - metadata +191 -0
 
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'victor'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Badge
         
     | 
| 
      
 4 
     | 
    
         
            +
              BADGE_OPTIONS = {
         
     | 
| 
      
 5 
     | 
    
         
            +
                up_to_date: {
         
     | 
| 
      
 6 
     | 
    
         
            +
                  bg_color: '#32CD32',
         
     | 
| 
      
 7 
     | 
    
         
            +
                  text: 'up-to-date',
         
     | 
| 
      
 8 
     | 
    
         
            +
                  width: 180
         
     | 
| 
      
 9 
     | 
    
         
            +
                },
         
     | 
| 
      
 10 
     | 
    
         
            +
                out_of_date: {
         
     | 
| 
      
 11 
     | 
    
         
            +
                  bg_color: '#ff0000',
         
     | 
| 
      
 12 
     | 
    
         
            +
                  text: 'out-of-date',
         
     | 
| 
      
 13 
     | 
    
         
            +
                  width: 190
         
     | 
| 
      
 14 
     | 
    
         
            +
                },
         
     | 
| 
      
 15 
     | 
    
         
            +
                unknown: {
         
     | 
| 
      
 16 
     | 
    
         
            +
                  bg_color: '#666',
         
     | 
| 
      
 17 
     | 
    
         
            +
                  text: 'unknown',
         
     | 
| 
      
 18 
     | 
    
         
            +
                  width: 170
         
     | 
| 
      
 19 
     | 
    
         
            +
                }
         
     | 
| 
      
 20 
     | 
    
         
            +
              }
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              def self.build_badge(status, num=nil)
         
     | 
| 
      
 23 
     | 
    
         
            +
                if ! [:up_to_date, :out_of_date, :unknown].include?(status)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  status = :unknown
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
                number = Integer(num) rescue nil if num
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                svg = Victor::SVG.new width: BADGE_OPTIONS[status][:width], height: 32, template: :html
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                style = {
         
     | 
| 
      
 31 
     | 
    
         
            +
                  stroke: '#d3d3d3',
         
     | 
| 
      
 32 
     | 
    
         
            +
                  stroke_width: 4
         
     | 
| 
      
 33 
     | 
    
         
            +
                }
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                svg.build do
         
     | 
| 
      
 36 
     | 
    
         
            +
                  rect x: 0, y: 0, width: BADGE_OPTIONS[status][:width], height: 32, fill: BADGE_OPTIONS[status][:bg_color], style: style
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  g font_size: 14, font_family: 'arial', fill: 'white' do
         
     | 
| 
      
 39 
     | 
    
         
            +
                    text "#{number} Dependencies #{BADGE_OPTIONS[status][:text]}", x: 10, y: 20
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
                return svg.render
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,123 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'ostruct'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'toml-rb'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class Gem::Dependency
         
     | 
| 
      
 5 
     | 
    
         
            +
              TYPES = [:runtime, :development, :build]
         
     | 
| 
      
 6 
     | 
    
         
            +
            end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module LanguageHelpers
         
     | 
| 
      
 9 
     | 
    
         
            +
              # Return a mock construct that mimicks Gem::Dependency for depedencies we tried to parse, but weren't valid.
         
     | 
| 
      
 10 
     | 
    
         
            +
              def unknown_dependency(name, type = :runtime)
         
     | 
| 
      
 11 
     | 
    
         
            +
                OpenStruct.new(name: name, type: type, requirement: 'unknown')
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              # For each dependency, find its latest version and return the two together. Takes account of invalid or dependencies (see #unknown_dependency)
         
     | 
| 
      
 15 
     | 
    
         
            +
              #
         
     | 
| 
      
 16 
     | 
    
         
            +
              # results - Array of Gem::Dependencies and unknown dependencies.
         
     | 
| 
      
 17 
     | 
    
         
            +
              #
         
     | 
| 
      
 18 
     | 
    
         
            +
              # Returns an Array of tuples of each dependency and its latest version: e.g., [[<Gem::Dependency>, <Gem::Version>]]
         
     | 
| 
      
 19 
     | 
    
         
            +
              def dependency_with_latest(results)
         
     | 
| 
      
 20 
     | 
    
         
            +
                results.map do |dependency|
         
     | 
| 
      
 21 
     | 
    
         
            +
                  [dependency, dependency.requirement.to_s == 'unknown' ? nil : latest_version(dependency.name)]
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def log_error(message)
         
     | 
| 
      
 26 
     | 
    
         
            +
                logger.error(message) if ::CuttingEdge::App.enable_logging
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            class Language
         
     | 
| 
      
 31 
     | 
    
         
            +
              include ::SemanticLogger::Loggable
         
     | 
| 
      
 32 
     | 
    
         
            +
              extend LanguageHelpers
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            module LanguageVersionHelpers
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              private
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def canonical_version(version)
         
     | 
| 
      
 40 
     | 
    
         
            +
                version.match(/^\./) ? "0#{version}" : version
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              # Translate wildcard (*) requirement to Ruby/Gem compatible requirement
         
     | 
| 
      
 44 
     | 
    
         
            +
              #
         
     | 
| 
      
 45 
     | 
    
         
            +
              # req - String version requirement
         
     | 
| 
      
 46 
     | 
    
         
            +
              #
         
     | 
| 
      
 47 
     | 
    
         
            +
              # Returns a translated String version requirement
         
     | 
| 
      
 48 
     | 
    
         
            +
              def translate_wildcard(req)
         
     | 
| 
      
 49 
     | 
    
         
            +
                if req =~ /!=/ # Negative Wildcard
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # Turn != 1.1.* into >= 1.2, < 1.1
         
     | 
| 
      
 51 
     | 
    
         
            +
                  req.sub!('.*', '.0')
         
     | 
| 
      
 52 
     | 
    
         
            +
                  req.sub!('!=', '')
         
     | 
| 
      
 53 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 54 
     | 
    
         
            +
                    v = Gem::Version.new(req) # Create the bumped version using Gem::Version so pre-release handling will work
         
     | 
| 
      
 55 
     | 
    
         
            +
                  rescue ArgumentError => e
         
     | 
| 
      
 56 
     | 
    
         
            +
                    return nil
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  lower_bound = ">= #{v.bump.version}"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  upper_bound = "< #{v.version}"
         
     | 
| 
      
 60 
     | 
    
         
            +
                  [lower_bound, upper_bound]
         
     | 
| 
      
 61 
     | 
    
         
            +
                elsif req =~ /^\s*\*\s*/
         
     | 
| 
      
 62 
     | 
    
         
            +
                  '>= 0' # Turn * into >= 0
         
     | 
| 
      
 63 
     | 
    
         
            +
                else
         
     | 
| 
      
 64 
     | 
    
         
            +
                  "~> #{req.sub('.*', '.0').sub('=', '')}" # Turn =1.1.* or 1.1.* into ~> 1.1.0
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
              # Translate SemVer Caret requirement to Ruby/Gem compatible requirement
         
     | 
| 
      
 69 
     | 
    
         
            +
              # Caret requirement:
         
     | 
| 
      
 70 
     | 
    
         
            +
              # lower bound = the unmodified version
         
     | 
| 
      
 71 
     | 
    
         
            +
              # upper bound = take the left most non-zero digit and +1 it
         
     | 
| 
      
 72 
     | 
    
         
            +
              #
         
     | 
| 
      
 73 
     | 
    
         
            +
              # req - String version requirement
         
     | 
| 
      
 74 
     | 
    
         
            +
              #
         
     | 
| 
      
 75 
     | 
    
         
            +
              # Returns a translated String version requirement
         
     | 
| 
      
 76 
     | 
    
         
            +
              def translate_caret(req)
         
     | 
| 
      
 77 
     | 
    
         
            +
                req.sub!('^', '')
         
     | 
| 
      
 78 
     | 
    
         
            +
                begin
         
     | 
| 
      
 79 
     | 
    
         
            +
                  version = Gem::Version.new(req)
         
     | 
| 
      
 80 
     | 
    
         
            +
                rescue ArgumentError => e
         
     | 
| 
      
 81 
     | 
    
         
            +
                  return nil
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
                segments = version.version.split('.')
         
     | 
| 
      
 84 
     | 
    
         
            +
                index = segments.find_index {|seg| seg.to_i > 0} # Find the leftmost non-zero digit.
         
     | 
| 
      
 85 
     | 
    
         
            +
                index = segments.rindex {|seg| seg.to_i == 0} unless index # If there is none, find the last 0.
         
     | 
| 
      
 86 
     | 
    
         
            +
                segments[index] = segments[index].to_i + 1
         
     | 
| 
      
 87 
     | 
    
         
            +
                upper_bound = segments[0..index].join('.')
         
     | 
| 
      
 88 
     | 
    
         
            +
                [">= #{version.version}", "< #{upper_bound}"]
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
              def parse_toml(content, sections)
         
     | 
| 
      
 92 
     | 
    
         
            +
                begin
         
     | 
| 
      
 93 
     | 
    
         
            +
                  config = TomlRB.parse(content)
         
     | 
| 
      
 94 
     | 
    
         
            +
                rescue TomlRB::ParseError => e
         
     | 
| 
      
 95 
     | 
    
         
            +
                  log_error("Encountered error when parsing TOML: #{e.class} #{e.message}")
         
     | 
| 
      
 96 
     | 
    
         
            +
                  return []
         
     | 
| 
      
 97 
     | 
    
         
            +
                end    
         
     | 
| 
      
 98 
     | 
    
         
            +
                results = []
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                sections.each do |dependency_type, section_name|
         
     | 
| 
      
 101 
     | 
    
         
            +
                  packages = config[section_name] || next
         
     | 
| 
      
 102 
     | 
    
         
            +
                  packages.each do |name, info|
         
     | 
| 
      
 103 
     | 
    
         
            +
                    requirement = info.fetch('version', nil) rescue info
         
     | 
| 
      
 104 
     | 
    
         
            +
                    if requirement
         
     | 
| 
      
 105 
     | 
    
         
            +
                      requirements = requirement.split(',').map {|req| translate_requirement(req)}
         
     | 
| 
      
 106 
     | 
    
         
            +
                      next if requirements.include?(nil) # If a sub-requirement failed to translate, skip this entire dependency.
         
     | 
| 
      
 107 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 108 
     | 
    
         
            +
                        results << Gem::Dependency.new(name, requirements, dependency_type)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      rescue StandardError => e
         
     | 
| 
      
 110 
     | 
    
         
            +
                        log_error("Encountered error when parsing requirement #{requirements}: #{e.class} #{e.message}")             
         
     | 
| 
      
 111 
     | 
    
         
            +
                        next
         
     | 
| 
      
 112 
     | 
    
         
            +
                      end
         
     | 
| 
      
 113 
     | 
    
         
            +
                    else
         
     | 
| 
      
 114 
     | 
    
         
            +
                      results << unknown_dependency(name, dependency_type)
         
     | 
| 
      
 115 
     | 
    
         
            +
                    end
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
                end
         
     | 
| 
      
 118 
     | 
    
         
            +
                results
         
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
            end
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            Dir[File.expand_path('../langs/*.rb', __FILE__)].each { |f| require f }
         
     | 
| 
         @@ -0,0 +1,102 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'http'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class PythonLang < Language
         
     | 
| 
      
 5 
     | 
    
         
            +
              # For Requirements.txt
         
     | 
| 
      
 6 
     | 
    
         
            +
              # See https://iscompatible.readthedocs.io/en/latest/
         
     | 
| 
      
 7 
     | 
    
         
            +
              COMPARATORS = />=|>|<=|<|==/
         
     | 
| 
      
 8 
     | 
    
         
            +
              VERSION_NUM = /\d[\.\w]*/
         
     | 
| 
      
 9 
     | 
    
         
            +
              SUFFIX_OPTION = /\s*(\[.*\])?/
         
     | 
| 
      
 10 
     | 
    
         
            +
              NAME = /[^,]+/
         
     | 
| 
      
 11 
     | 
    
         
            +
              REGEX = /^(#{NAME})\s*(#{COMPARATORS})\s*(#{VERSION_NUM})(\s*,\s*(#{COMPARATORS})\s*(#{VERSION_NUM}))?#{SUFFIX_OPTION}$/
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              API_URL = 'https://pypi.org/pypi/'
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              PIPFILE_SECTIONS = {
         
     | 
| 
      
 16 
     | 
    
         
            +
                :runtime => 'packages',
         
     | 
| 
      
 17 
     | 
    
         
            +
                :development => 'dev-packages'
         
     | 
| 
      
 18 
     | 
    
         
            +
              }
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              extend LanguageVersionHelpers
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                # Defaults for projects in this language
         
     | 
| 
      
 25 
     | 
    
         
            +
                def locations(name = nil)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  ['requirements.txt', 'Pipfile']
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # Parse a dependency file
         
     | 
| 
      
 30 
     | 
    
         
            +
                #
         
     | 
| 
      
 31 
     | 
    
         
            +
                # name - String contents of the file
         
     | 
| 
      
 32 
     | 
    
         
            +
                # content - String contents of the file
         
     | 
| 
      
 33 
     | 
    
         
            +
                #
         
     | 
| 
      
 34 
     | 
    
         
            +
                # Returns an Array of tuples of each dependency and its latest version: [[<Gem::Dependency>, <Gem::Version>]]
         
     | 
| 
      
 35 
     | 
    
         
            +
                def parse_file(name, content)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  return nil unless content
         
     | 
| 
      
 37 
     | 
    
         
            +
                  if name =~ /\.txt$/
         
     | 
| 
      
 38 
     | 
    
         
            +
                    results = parse_requirements(content)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  elsif name =~ /Pipfile/
         
     | 
| 
      
 40 
     | 
    
         
            +
                    results = parse_toml(content, PIPFILE_SECTIONS)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  dependency_with_latest(results) if results
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def parse_requirements(content)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  results = []
         
     | 
| 
      
 47 
     | 
    
         
            +
                  content.each_line do |line|
         
     | 
| 
      
 48 
     | 
    
         
            +
                    next if line =~ /^\s*-e/ # ignore 'editable' dependencies
         
     | 
| 
      
 49 
     | 
    
         
            +
                    if line =~ COMPARATORS
         
     | 
| 
      
 50 
     | 
    
         
            +
                      next unless match = line.match(REGEX) # Skip this line if it doesn't conform to our expectations
         
     | 
| 
      
 51 
     | 
    
         
            +
                      name, first_comp, first_version, _ignore, second_comp, second_version = match.captures
         
     | 
| 
      
 52 
     | 
    
         
            +
                      first_comp = '=' if first_comp == '=='
         
     | 
| 
      
 53 
     | 
    
         
            +
                      second_comp = '=' if second_comp == '=='
         
     | 
| 
      
 54 
     | 
    
         
            +
                      dep = Gem::Dependency.new(name, "#{first_comp} #{first_version}")
         
     | 
| 
      
 55 
     | 
    
         
            +
                      dep.requirement.concat(["#{second_comp} #{second_version}"]) if second_comp && second_version
         
     | 
| 
      
 56 
     | 
    
         
            +
                    else
         
     | 
| 
      
 57 
     | 
    
         
            +
                      dep = Gem::Dependency.new(line.strip) # requries version to be >= 0
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
                    results << dep
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                  results
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                # Find the latest versions of a dependency by name
         
     | 
| 
      
 65 
     | 
    
         
            +
                #
         
     | 
| 
      
 66 
     | 
    
         
            +
                # name - String name of the dependency
         
     | 
| 
      
 67 
     | 
    
         
            +
                #
         
     | 
| 
      
 68 
     | 
    
         
            +
                # Returns a Gem::Version
         
     | 
| 
      
 69 
     | 
    
         
            +
                def latest_version(name)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 71 
     | 
    
         
            +
                    content = HTTP.timeout(::CuttingEdge::LAST_VERSION_TIMEOUT).follow(max_hops: 1).get(::File.join(API_URL, name, 'json')).parse
         
     | 
| 
      
 72 
     | 
    
         
            +
                    version = content['info']['version']
         
     | 
| 
      
 73 
     | 
    
         
            +
                    Gem::Version.new(canonical_version(version))
         
     | 
| 
      
 74 
     | 
    
         
            +
                  rescue StandardError, HTTP::TimeoutError => e
         
     | 
| 
      
 75 
     | 
    
         
            +
                    log_error("Encountered error when fetching latest version of #{name}: #{e.class} #{e.message}")
         
     | 
| 
      
 76 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                # Translate version requirement syntax for Pipfiles to a String or Array of Strings that Gem::Dependency.new understands
         
     | 
| 
      
 81 
     | 
    
         
            +
                # Pipfile support * and != requirements, which Ruby does not
         
     | 
| 
      
 82 
     | 
    
         
            +
                # See https://www.python.org/dev/peps/pep-0440/#version-matching
         
     | 
| 
      
 83 
     | 
    
         
            +
                # 
         
     | 
| 
      
 84 
     | 
    
         
            +
                # req - String version requirement
         
     | 
| 
      
 85 
     | 
    
         
            +
                #
         
     | 
| 
      
 86 
     | 
    
         
            +
                # Returns a translated String version requirement
         
     | 
| 
      
 87 
     | 
    
         
            +
                def translate_requirement(req)
         
     | 
| 
      
 88 
     | 
    
         
            +
                req.sub!('~=', '~>')
         
     | 
| 
      
 89 
     | 
    
         
            +
                req.sub!('==', '=')
         
     | 
| 
      
 90 
     | 
    
         
            +
                  case req
         
     | 
| 
      
 91 
     | 
    
         
            +
                  when /\*/
         
     | 
| 
      
 92 
     | 
    
         
            +
                    translate_wildcard(req)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  when '!='
         
     | 
| 
      
 94 
     | 
    
         
            +
                    req.sub!('!=', '')
         
     | 
| 
      
 95 
     | 
    
         
            +
                    ["< #{req}", "> #{req}"]
         
     | 
| 
      
 96 
     | 
    
         
            +
                  else
         
     | 
| 
      
 97 
     | 
    
         
            +
                    req
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'gemnasium/parser'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class RubyLang < Language
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # Defaults for projects in this language
         
     | 
| 
      
 9 
     | 
    
         
            +
                def locations(name)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  ["#{name}.gemspec", 'Gemfile']
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # Parse a dependency file
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                # name - String contents of the file
         
     | 
| 
      
 16 
     | 
    
         
            +
                # content - String contents of the file
         
     | 
| 
      
 17 
     | 
    
         
            +
                #
         
     | 
| 
      
 18 
     | 
    
         
            +
                # Returns an Array of tuples of each dependency and its latest version: [[<Bundler::Dependency>, <Gem::Version>]]
         
     | 
| 
      
 19 
     | 
    
         
            +
                def parse_file(name, content)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  return nil unless content
         
     | 
| 
      
 21 
     | 
    
         
            +
                  results = name =~ /gemspec/ ? parse_gemspec(content) : parse_gemfile(content)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  dependency_with_latest(results)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def latest_version(name)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # Fancy todo: cache these?
         
     | 
| 
      
 27 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Gem::SpecFetcher.fetcher.spec_for_dependency(Gem::Dependency.new(name, nil)).flatten.first.version
         
     | 
| 
      
 29 
     | 
    
         
            +
                  rescue StandardError => e
         
     | 
| 
      
 30 
     | 
    
         
            +
                    log_error("Encountered error when fetching latest version of #{name}: #{e.class} #{e.message}")
         
     | 
| 
      
 31 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def parse_ruby(type, content)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  Gemnasium::Parser.send(type, content).dependencies
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def parse_gemspec(content)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  parse_ruby(:gemspec, content)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def parse_gemfile(content)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  parse_ruby(:gemfile, content)
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,68 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'http'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class RustLang < Language
         
     | 
| 
      
 5 
     | 
    
         
            +
              API_URL = 'https://crates.io/api/v1/crates'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              CARGOFILE_SECTIONS = {
         
     | 
| 
      
 8 
     | 
    
         
            +
                :runtime => 'dependencies',
         
     | 
| 
      
 9 
     | 
    
         
            +
                :development => 'dev-dependencies',
         
     | 
| 
      
 10 
     | 
    
         
            +
                :build => 'build-dependencies'
         
     | 
| 
      
 11 
     | 
    
         
            +
              }
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              extend LanguageVersionHelpers
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Defaults for projects in this language
         
     | 
| 
      
 18 
     | 
    
         
            +
                def locations(name = nil)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  ['Cargo.toml']
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # Parse a dependency file
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                # name - String contents of the file
         
     | 
| 
      
 25 
     | 
    
         
            +
                # content - String contents of the file
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                # Returns an Array of tuples of each dependency and its latest version: [[<Gem::Dependency>, <Gem::Version>]]
         
     | 
| 
      
 28 
     | 
    
         
            +
                def parse_file(name, content)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  return nil unless content
         
     | 
| 
      
 30 
     | 
    
         
            +
                  results = parse_toml(content, CARGOFILE_SECTIONS)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  dependency_with_latest(results) if results
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                # Find the latest versions of a dependency by name
         
     | 
| 
      
 35 
     | 
    
         
            +
                #
         
     | 
| 
      
 36 
     | 
    
         
            +
                # name - String name of the dependency
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
      
 38 
     | 
    
         
            +
                # Returns a Gem::Version
         
     | 
| 
      
 39 
     | 
    
         
            +
                def latest_version(name)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 41 
     | 
    
         
            +
                    content = HTTP.timeout(::CuttingEdge::LAST_VERSION_TIMEOUT).get(::File.join(API_URL, name)).parse
         
     | 
| 
      
 42 
     | 
    
         
            +
                    version = content['crate']['max_version']
         
     | 
| 
      
 43 
     | 
    
         
            +
                    Gem::Version.new(canonical_version(version))
         
     | 
| 
      
 44 
     | 
    
         
            +
                  rescue StandardError, HTTP::Error => e
         
     | 
| 
      
 45 
     | 
    
         
            +
                    log_error("Encountered error when fetching latest version of #{name}: #{e.class} #{e.message}")
         
     | 
| 
      
 46 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Translate Cargo version requirement syntax to a String or Array of Strings that Gem::Dependency.new understands
         
     | 
| 
      
 51 
     | 
    
         
            +
                # Cargo.toml files support * and ^ (wildcard and caret) requirements, which Ruby does not
         
     | 
| 
      
 52 
     | 
    
         
            +
                # See: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
         
     | 
| 
      
 53 
     | 
    
         
            +
                # 
         
     | 
| 
      
 54 
     | 
    
         
            +
                # req - String version requirement
         
     | 
| 
      
 55 
     | 
    
         
            +
                #
         
     | 
| 
      
 56 
     | 
    
         
            +
                # Returns a translated String version requirement
         
     | 
| 
      
 57 
     | 
    
         
            +
                def translate_requirement(req)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  if req =~ /~|<|>|\*|=/
         
     | 
| 
      
 59 
     | 
    
         
            +
                    return translate_wildcard(req) if req =~ /\*/
         
     | 
| 
      
 60 
     | 
    
         
            +
                    req.sub!('~', '~>')
         
     | 
| 
      
 61 
     | 
    
         
            +
                    req
         
     | 
| 
      
 62 
     | 
    
         
            +
                  else
         
     | 
| 
      
 63 
     | 
    
         
            +
                    translate_caret(req)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.expand_path('../langs.rb', __FILE__)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module CuttingEdge
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Repository
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                DEPENDENCY_TYPES = [:runtime] # Which dependency types to accept (default only :runtime, excludes :development).
         
     | 
| 
      
 7 
     | 
    
         
            +
                DEFAULT_LANG = 'ruby'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                attr_reader :token, :locations, :lang
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_accessor :dependency_types
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(org, name, lang = nil, locations = nil, branch = nil, token = nil)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @org     = org
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @name    = name
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @branch  = branch  || 'master'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @token   = token
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @lang    = lang || DEFAULT_LANG
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @locations = {}
         
     | 
| 
      
 19 
     | 
    
         
            +
                  (locations || get_lang(@lang).locations(name)).each do |loc|
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @locations[loc] = url_for_file(loc)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @dependency_types = DEPENDENCY_TYPES
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def source
         
     | 
| 
      
 26 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def identifier
         
     | 
| 
      
 30 
     | 
    
         
            +
                  File.join(source, @org, @name)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def url_for_file(file)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  file
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                private
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def get_lang(lang)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  Object.const_get("::#{lang.capitalize}Lang")
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              class GithubRepository < Repository
         
     | 
| 
      
 45 
     | 
    
         
            +
                HOST = 'https://raw.githubusercontent.com'
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def source
         
     | 
| 
      
 48 
     | 
    
         
            +
                  'github'
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def url_for_file(file)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  File.join(HOST, @org, @name, @branch, file)
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              class GitlabRepository < Repository
         
     | 
| 
      
 57 
     | 
    
         
            +
                HOST = 'https://gitlab.com/'
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def source
         
     | 
| 
      
 60 
     | 
    
         
            +
                  'gitlab'
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def url_for_file(file)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  File.join(HOST, @org, @name, 'raw', @branch, file)
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     |