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.
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+
3
+ module VersionRequirementComparator
4
+
5
+ # Operators that require the checked version to be higher than the required version
6
+ HIGHER_THAN = ['>', '>=']
7
+
8
+ def version_requirement_diff(requirement, latest_version)
9
+ segments = latest_version.canonical_segments
10
+
11
+ # Since we know `latest_version` is higher than the requirement, we can and must ignore cases where the constraint fails because `check_version` is lower
12
+ # Such cases would be caused by the fact that we are generating `check_version` below by chopping off the version decimals after `position`
13
+ # For example: `latest_version` is 3.5. On the first pass of this loop, we will thus be checking version 3.0.
14
+ # This would fail a ~> 3.2 requirement on the first pass, falsely yielding an `:outdated_major`.
15
+ # Therefore, filter out the HIGHER_THAN operators
16
+ constraints = requirement.requirements.map do |comparator, version|
17
+ if comparator == '~>'
18
+ Gem::Requirement.new("< #{version.bump.to_s}") # Use only the upper bound requirement of a ~> comparison
19
+ elsif !HIGHER_THAN.include?(comparator)
20
+ Gem::Requirement.new("#{comparator} #{version.to_s}")
21
+ end
22
+ end
23
+ constraints.compact!
24
+
25
+ segments.each_with_index do |_v, position|
26
+ check_version = Gem::Version.new(segments[0..position].join('.'))
27
+ constraints.each do |constraint|
28
+ unless constraint.satisfied_by?(check_version)
29
+ return version_difference_type(position)
30
+ end
31
+ end
32
+ end
33
+ return :unknown # This can occur if latest_version is actually lower than the requirement (e.g., when a newer version of a gem has been yanked)
34
+ end
35
+
36
+ def version_difference_type(position)
37
+ case position
38
+ when 0
39
+ :outdated_major
40
+ when 1
41
+ :outdated_minor
42
+ else
43
+ :outdated_patch
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../../badge.rb', __FILE__)
2
+ require File.expand_path('../helpers.rb', __FILE__)
3
+
4
+ class BadgeWorker < GenericWorker
5
+
6
+ def perform(identifier)
7
+ log_info 'Running Worker!'
8
+ dependencies = get_from_store(identifier)
9
+ if dependencies
10
+ status = generate_status(dependencies[:outdated])
11
+ add_to_store("svg-#{identifier}", Badge.build_badge(status, dependencies[:outdated_total]))
12
+ end
13
+ GC.start
14
+ end
15
+
16
+ private
17
+
18
+ def generate_status(status)
19
+ # status is more specific than Badge.build_badge currently expects, so make it a bit less so
20
+ status == :up_to_date ? status : :out_of_date
21
+ end
22
+ end
@@ -0,0 +1,100 @@
1
+ require 'rubygems'
2
+ require 'http'
3
+ require 'moneta'
4
+ require File.expand_path('../../versions.rb', __FILE__)
5
+ require File.expand_path('../../langs.rb', __FILE__)
6
+ require File.expand_path('../helpers.rb', __FILE__)
7
+
8
+ class DependencyWorker < GenericWorker
9
+ include VersionRequirementComparator
10
+
11
+ # Order is significant for purposes of calculating results[:outdated]
12
+ STATUS_TYPES = [:outdated_major, :outdated_minor, :outdated_patch, :ok, :no_requirement, :unknown]
13
+ OUTDATED_TYPES = STATUS_TYPES[0..-3]
14
+
15
+ def perform(identifier, lang, locations, dependency_types)
16
+ log_info 'Running Worker!'
17
+ dependencies = {}
18
+ locations.each do |name, url|
19
+ contents = http_get(url)
20
+ dependencies[name] = get_results(get_lang(lang).parse_file(name, contents), dependency_types)
21
+ end
22
+ dependencies.merge!(generate_stats(dependencies))
23
+ add_to_store(identifier, dependencies)
24
+ GC.start
25
+ end
26
+
27
+ private
28
+
29
+ def get_results(dependencies, dependency_types)
30
+ results = {}
31
+ STATUS_TYPES.each {|type| results[type] = []}
32
+ if dependencies
33
+ dependencies.select! {|dep| dependency_types.include?(dep.first.type)}
34
+ dependencies.each do |dep, latest_version|
35
+ dependency_hash = dependency(dep.name, dep.requirement.to_s, latest_version.to_s, dep.type)
36
+ if dependency_hash[:required] == 'unknown'
37
+ results[:no_requirement] << dependency_hash
38
+ elsif latest_version.nil?
39
+ results[:unknown] << dependency_hash
40
+ elsif is_outdated?(dep, latest_version)
41
+ outdated = version_requirement_diff(dep.requirement, latest_version.respond_to?(:last) ? latest_version.last : latest_version)
42
+ results[outdated] << dependency_hash
43
+ else
44
+ results[:ok] << dependency_hash
45
+ end
46
+ end
47
+ end
48
+ results
49
+ end
50
+
51
+ def generate_stats(locations)
52
+ results = {}
53
+ STATUS_TYPES.each do |type|
54
+ num = stats(type, locations)
55
+ results[type] = num
56
+ if OUTDATED_TYPES.include?(type)
57
+ results[:outdated_total] = results[:outdated_total].to_i + num
58
+ results[:outdated] = type unless results[:outdated] || num == 0
59
+ end
60
+ end
61
+ results[:outdated] = :unknown if results[:outdated_total] == 0 && results[:ok] == 0
62
+ results[:outdated] = :up_to_date unless results[:outdated]
63
+ results
64
+ end
65
+
66
+ def stats(stat, locations)
67
+ sum = 0
68
+ locations.each do |name, dependencies|
69
+ sum = sum + dependencies[stat].length
70
+ end
71
+ sum
72
+ end
73
+
74
+ def dependency(name, requirement, latest, type)
75
+ {
76
+ :name => name,
77
+ :required => requirement,
78
+ :latest => latest,
79
+ :type => type
80
+ }
81
+ end
82
+
83
+ def get_lang(lang)
84
+ Object.const_get("#{lang.capitalize}Lang")
85
+ end
86
+
87
+ def http_get(url)
88
+ begin
89
+ response = HTTP.get(url)
90
+ response.status == 200 ? response.to_s : nil
91
+ rescue HTTP::TimeoutError => e
92
+ log_info("Encountered error when fetching latest version of #{name}: #{e.class} #{e.message}")
93
+ end
94
+ end
95
+
96
+ def is_outdated?(dependency, latest_version)
97
+ !dependency.requirement.satisfied_by?(latest_version)
98
+ end
99
+
100
+ end
@@ -0,0 +1,24 @@
1
+ require 'sinatra/logger'
2
+ require 'sucker_punch'
3
+
4
+ module WorkerHelpers
5
+
6
+ def log_info(message)
7
+ logger.info(message) if ::CuttingEdge::App.enable_logging
8
+ end
9
+
10
+ def add_to_store(identifier, dependencies)
11
+ ::CuttingEdge::App.store[identifier] = dependencies
12
+ end
13
+
14
+ def get_from_store(identifier)
15
+ ::CuttingEdge::App.store[identifier]
16
+ end
17
+
18
+ end
19
+
20
+ class GenericWorker
21
+ include ::SuckerPunch::Job
22
+ include ::WorkerHelpers
23
+ include ::SemanticLogger::Loggable
24
+ end
@@ -0,0 +1,89 @@
1
+ REQUIREMENT_TXT = <<EOF
2
+ requests>=2.0
3
+ oauthlib
4
+ requests-oauthlib>=1.0.0 [PDF]
5
+ -e svn+http://myrepo/svn/MyApp#egg=MyApp
6
+ Flask>=0.7
7
+ urlobject==1.0
8
+ six
9
+ EOF
10
+
11
+ # See https://www.python.org/dev/peps/pep-0440/#version-exclusion
12
+ # And https://github.com/pypa/pipfile
13
+ PIPFILE = <<EOF
14
+ [[source]]
15
+ url = 'https://pypi.python.org/simple'
16
+ verify_ssl = true
17
+ name = 'pypi'
18
+
19
+ [requires]
20
+ python_version = '2.7'
21
+
22
+ [packages]
23
+ requests = { extras = ['socks'] }
24
+ records = '>0.5.0'
25
+ foo = '!= 0.5.*'
26
+ bar = '~=0.2'
27
+ baz = '=0.3.*'
28
+ django = { git = 'https://github.com/django/django.git', ref = '1.11.4', editable = true }
29
+ "e682b37" = {file = "https://github.com/divio/django-cms/archive/release/3.4.x.zip"}
30
+ "e1839a8" = {path = ".", editable = true}
31
+ pywinusb = { version = "*", os_name = "=='nt'", index="pypi"}
32
+
33
+ [dev-packages]
34
+ nose = '*'
35
+ unittest2 = {version = ">=1.0,<3.0", markers="python_version < '2.7.9' or (python_version >= '3.0' and python_version < '3.4')"}
36
+ EOF
37
+
38
+ describe PythonLang do
39
+ context 'requirements.txt' do
40
+ it 'expects the default dependency files to be requirements.txt and Pipfile' do
41
+ expect(PythonLang.locations).to eq ['requirements.txt', 'Pipfile']
42
+ end
43
+
44
+ it 'parses requirements.txt' do
45
+ result = PythonLang.parse_file('requirements.txt', REQUIREMENT_TXT)
46
+ expect(result).to be_a Array
47
+ expect(result.length).to eq 6
48
+ result.each do |dep, version|
49
+ expect(dep).to be_a Gem::Dependency
50
+ expect(dep.type).to eq :runtime
51
+ expect(version.version).to match /\d\.\d.*/
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'pipfile' do
57
+ it 'fails softly on invalid Pipfile' do
58
+ expect(PythonLang.parse_file('Pipfile', 'waa')).to eq []
59
+ end
60
+
61
+ it 'parses Pipefile' do
62
+ result = PythonLang.parse_file('Pipfile', PIPFILE)
63
+ expect(result).to be_a Array
64
+
65
+ dev = result.select {|r| r.first.type == :development}
66
+ run = result.select {|r| r.first.type == :runtime}
67
+ expect(dev.length).to eq 2
68
+ expect(run.length).to eq 9
69
+
70
+ expect(run.first.first).to be_a OpenStruct
71
+ expect(run.first.first.requirement).to eq 'unknown'
72
+ expect(run.first.last).to be_nil
73
+ expect(dev.first.first.requirement.to_s).to eq '>= 0'
74
+ expect(dev.first.last.version).to match /\d\.\d.*/
75
+
76
+ foo = result.find {|r| r.first.name == 'foo'}
77
+ bar = result.find {|r| r.first.name == 'bar'}
78
+ baz = result.find {|r| r.first.name == 'baz'}
79
+
80
+ foo_req = foo.first.requirement.to_s.split(',')
81
+ foo_req.map! {|r| r.strip}
82
+ expect(foo_req).to include('< 0.5.0')
83
+ expect(foo_req).to include('>= 0.6')
84
+
85
+ expect(bar.first.requirement.to_s).to eq '~> 0.2'
86
+ expect(baz.first.requirement.to_s).to eq '~> 0.3.0'
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,81 @@
1
+ CARGO = <<EOF
2
+ [dependencies]
3
+ log = { version = "0.4.*", features = ["std"] }
4
+ regex = { version = "1.0.3", optional = true }
5
+ termcolor = { version = "^1.0.2", optional = true }
6
+ humantime = { version = "~1.3", optional = true }
7
+ atty = { version = "0.2.5", optional = true }
8
+ my-library = { git = 'https://example.com/git/my-library' }
9
+ uuid = "1.0"
10
+
11
+ [dev-dependencies]
12
+ tempdir = "0.3"
13
+
14
+ [build-dependencies]
15
+ cc = "1.0.3"
16
+ EOF
17
+
18
+ def translate_req(str)
19
+ RustLang.send(:translate_requirement, str)
20
+ end
21
+
22
+ describe RustLang do
23
+ it 'expects the default dependency files to be Cargo.toml' do
24
+ expect(RustLang.locations).to eq ['Cargo.toml']
25
+ end
26
+
27
+ it 'parses Cargo.toml' do
28
+ results = RustLang.parse_file('Cargo.toml', CARGO)
29
+ expect(results.length).to eq 9
30
+
31
+ dev = results.select {|r| r.first.type == :development}
32
+ run = results.select {|r| r.first.type == :runtime}
33
+ build = results.select {|r| r.first.type == :build}
34
+
35
+ expect(dev.length).to eq 1
36
+ expect(run.length).to eq 7
37
+ expect(build.length).to eq 1
38
+
39
+ expect(run[5].first).to be_a OpenStruct
40
+ expect(run[5].first.requirement).to eq 'unknown'
41
+ expect(run[5].last).to be_nil
42
+ expect(dev.first.first.requirement.to_s).to match />= 0\.3/
43
+ expect(dev.first.first.requirement.to_s).to match /< 0\.4/
44
+ expect(dev.first.last.version).to match /\d\.\d.*/
45
+ end
46
+
47
+ context 'translates Rust version requirements to Gem:: compatible requirements' do
48
+ # See https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
49
+ it 'for the caret operator' do
50
+ # ^1.2.3 := >=1.2.3, <2.0.0
51
+ # ^1.2 := >=1.2.0, <2.0.0
52
+ # ^1 := >=1.0.0, <2.0.0
53
+ # ^0.2.3 := >=0.2.3, <0.3.0
54
+ # ^0.2 := >=0.2.0, <0.3.0
55
+ # ^0.0.3 := >=0.0.3, <0.0.4
56
+ # ^0.0 := >=0.0.0, <0.1.0
57
+ # ^0 := >=0.0.0, <1.0.0
58
+ expect(translate_req('^1.2.3')).to eq ['>= 1.2.3', '< 2']
59
+ expect(translate_req('^1.2')).to eq ['>= 1.2', '< 2']
60
+ expect(translate_req('^1')).to eq ['>= 1', '< 2']
61
+ expect(translate_req('^0.2.3')).to eq ['>= 0.2.3', '< 0.3']
62
+ expect(translate_req('^0.2')).to eq ['>= 0.2', '< 0.3']
63
+ expect(translate_req('^0.0.3')).to eq ['>= 0.0.3', '< 0.0.4']
64
+ expect(translate_req('^0.0')).to eq ['>= 0.0', '< 0.1']
65
+ expect(translate_req('^0')).to eq ['>= 0', '< 1']
66
+ end
67
+
68
+ it 'for the ~ operator' do
69
+ expect(translate_req('~1.2.3')). to eq '~>1.2.3'
70
+ end
71
+
72
+ it 'for the * operator' do
73
+ # * := >=0.0.0 := >= 0
74
+ # 1.* := >=1.0.0, <2.0.0 := ~> 1.0
75
+ # 1.2.* := >=1.2.0, <1.3.0 := ~> 1.2.0
76
+ expect(translate_req('*')).to eq '>= 0'
77
+ expect(translate_req('1.*')).to eq '~> 1.0'
78
+ expect(translate_req('1.2.*')).to eq '~> 1.2.0'
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,111 @@
1
+ require 'simplecov'
2
+
3
+ if ENV['TRAVIS']
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+ end
7
+
8
+ require File.expand_path('../../lib/cutting_edge/app.rb', __FILE__)
9
+ CuttingEdge::App.set(:enable_logging, false)
10
+ ::SemanticLogger.add_appender(io: STDOUT)
11
+
12
+ # This file was generated by the `rspec --init` command. Conventionally, all
13
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
14
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
15
+ # this file to always be loaded, without a need to explicitly require it in any
16
+ # files.
17
+ #
18
+ # Given that it is always loaded, you are encouraged to keep this file as
19
+ # light-weight as possible. Requiring heavyweight dependencies from this file
20
+ # will add to the boot time of your test suite on EVERY test run, even for an
21
+ # individual file that may not need all of that loaded. Instead, consider making
22
+ # a separate helper file that requires the additional dependencies and performs
23
+ # the additional setup, and require it from the spec files that actually need
24
+ # it.
25
+ #
26
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
27
+ RSpec.configure do |config|
28
+ # rspec-expectations config goes here. You can use an alternate
29
+ # assertion/expectation library such as wrong or the stdlib/minitest
30
+ # assertions if you prefer.
31
+ config.expect_with :rspec do |expectations|
32
+ # This option will default to `true` in RSpec 4. It makes the `description`
33
+ # and `failure_message` of custom matchers include text for helper methods
34
+ # defined using `chain`, e.g.:
35
+ # be_bigger_than(2).and_smaller_than(4).description
36
+ # # => "be bigger than 2 and smaller than 4"
37
+ # ...rather than:
38
+ # # => "be bigger than 2"
39
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
40
+ end
41
+
42
+ # rspec-mocks config goes here. You can use an alternate test double
43
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
44
+ config.mock_with :rspec do |mocks|
45
+ # Prevents you from mocking or stubbing a method that does not exist on
46
+ # a real object. This is generally recommended, and will default to
47
+ # `true` in RSpec 4.
48
+ mocks.verify_partial_doubles = true
49
+ end
50
+
51
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
52
+ # have no way to turn it off -- the option exists only for backwards
53
+ # compatibility in RSpec 3). It causes shared context metadata to be
54
+ # inherited by the metadata hash of host groups and examples, rather than
55
+ # triggering implicit auto-inclusion in groups with matching metadata.
56
+ config.shared_context_metadata_behavior = :apply_to_host_groups
57
+
58
+ # The settings below are suggested to provide a good initial experience
59
+ # with RSpec, but feel free to customize to your heart's content.
60
+ =begin
61
+ # This allows you to limit a spec run to individual examples or groups
62
+ # you care about by tagging them with `:focus` metadata. When nothing
63
+ # is tagged with `:focus`, all examples get run. RSpec also provides
64
+ # aliases for `it`, `describe`, and `context` that include `:focus`
65
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
66
+ config.filter_run_when_matching :focus
67
+
68
+ # Allows RSpec to persist some state between runs in order to support
69
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
70
+ # you configure your source control system to ignore this file.
71
+ config.example_status_persistence_file_path = "spec/examples.txt"
72
+
73
+ # Limits the available syntax to the non-monkey patched syntax that is
74
+ # recommended. For more details, see:
75
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
76
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
77
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
78
+ config.disable_monkey_patching!
79
+
80
+ # This setting enables warnings. It's recommended, but in some cases may
81
+ # be too noisy due to issues in dependencies.
82
+ config.warnings = true
83
+
84
+ # Many RSpec users commonly either run the entire suite or an individual
85
+ # file, and it's useful to allow more verbose output when running an
86
+ # individual spec file.
87
+ if config.files_to_run.one?
88
+ # Use the documentation formatter for detailed output,
89
+ # unless a formatter has already been configured
90
+ # (e.g. via a command-line flag).
91
+ config.default_formatter = "doc"
92
+ end
93
+
94
+ # Print the 10 slowest examples and example groups at the
95
+ # end of the spec run, to help surface which specs are running
96
+ # particularly slow.
97
+ config.profile_examples = 10
98
+
99
+ # Run specs in random order to surface order dependencies. If you find an
100
+ # order dependency and want to debug it, you can fix the order by providing
101
+ # the seed, which is printed after each run.
102
+ # --seed 1234
103
+ config.order = :random
104
+
105
+ # Seed global randomization in this process using the `--seed` CLI option.
106
+ # Setting this allows you to use `--seed` to deterministically reproduce
107
+ # test failures related to randomization by passing the same `--seed` value
108
+ # as the one that triggered the failure.
109
+ Kernel.srand config.seed
110
+ =end
111
+ end