Class: PythonLang

Inherits:
Language show all
Extended by:
LanguageVersionHelpers
Defined in:
lib/cutting_edge/langs/python.rb

Constant Summary collapse

COMPARATORS =

For Requirements.txt See iscompatible.readthedocs.io/en/latest/

/>=|>|<=|<|==/
VERSION_NUM =
/\d[\.\w]*/
SUFFIX_OPTION =
/\s*(\[.*\])?/
NAME =
/[^,]+/
REGEX =
/^(#{NAME})\s*(#{COMPARATORS})\s*(#{VERSION_NUM})(\s*,\s*(#{COMPARATORS})\s*(#{VERSION_NUM}))?#{SUFFIX_OPTION}$/
API_URL =
'https://pypi.org/pypi/'
PIPFILE_SECTIONS =
{
  :runtime => 'packages',
  :development => 'dev-packages'
}

Class Method Summary collapse

Methods inherited from Language

#website

Methods included from LanguageHelpers

#dependency_with_latest, #log_error, #unknown_dependency

Class Method Details

.latest_version(name) ⇒ Object

Find the latest versions of a dependency by name

name - String name of the dependency

Returns a Gem::Version



73
74
75
76
77
78
79
80
81
82
# File 'lib/cutting_edge/langs/python.rb', line 73

def latest_version(name)
  begin
    content = HTTP.timeout(::CuttingEdge::LAST_VERSION_TIMEOUT).follow(max_hops: 1).get(::File.join(API_URL, name, 'json')).parse
    version = content['info']['version']
    Gem::Version.new(canonical_version(version))
  rescue StandardError, HTTP::TimeoutError => e
    log_error("Encountered error when fetching latest version of #{name}: #{e.class} #{e.message}")
    nil
  end
end

.locations(name = nil) ⇒ Object

Defaults for projects in this language



25
26
27
# File 'lib/cutting_edge/langs/python.rb', line 25

def locations(name = nil)
  ['requirements.txt', 'Pipfile']
end

.parse_file(name, content) ⇒ Object

Parse a dependency file

name - String contents of the file content - String contents of the file

Returns an Array of tuples of each dependency and its latest version: [[<Gem::Dependency>, <Gem::Version>]]



39
40
41
42
43
44
45
46
47
# File 'lib/cutting_edge/langs/python.rb', line 39

def parse_file(name, content)
  return nil unless content
  if name =~ /\.txt$/
    results = parse_requirements(content)
  elsif name =~ /Pipfile/
    results = parse_toml(content, PIPFILE_SECTIONS)
  end
  dependency_with_latest(results) if results
end

.parse_requirements(content) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/cutting_edge/langs/python.rb', line 49

def parse_requirements(content)
  results = []
  content.each_line do |line|
    next if line =~ /^\s*-e/ # ignore 'editable' dependencies
    if line =~ COMPARATORS
      next unless match = line.match(REGEX) # Skip this line if it doesn't conform to our expectations
      name, first_comp, first_version, _ignore, second_comp, second_version = match.captures
      first_comp = '=' if first_comp == '=='
      second_comp = '=' if second_comp == '=='
      dep = Gem::Dependency.new(name.strip, "#{first_comp} #{first_version}")
      dep.requirement.concat(["#{second_comp} #{second_version}"]) if second_comp && second_version
    else
      dep = Gem::Dependency.new(line.strip) # requries version to be >= 0
    end
    results << dep
  end
  results
end

.translate_requirement(req) ⇒ Object

Translate version requirement syntax for Pipfiles to a String or Array of Strings that Gem::Dependency.new understands Pipfile support * and != requirements, which Ruby does not See www.python.org/dev/peps/pep-0440/#version-matching

req - String version requirement

Returns a translated String version requirement



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/cutting_edge/langs/python.rb', line 91

def translate_requirement(req)
req.sub!('~=', '~>')
req.sub!('==', '=')
  case req
  when /\*/
    translate_wildcard(req)
  when '!='
    req.sub!('!=', '')
    ["< #{req}", "> #{req}"]
  else
    req
  end
end

.website(name) ⇒ Object



29
30
31
# File 'lib/cutting_edge/langs/python.rb', line 29

def website(name)
  "https://pypi.org/project/#{name}"
end