ossert 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.rubocop_todo.yml +44 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +16 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +199 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/config/classifiers.yml +153 -0
  15. data/config/descriptions.yml +45 -0
  16. data/config/sidekiq.rb +15 -0
  17. data/config/stats.yml +198 -0
  18. data/config/translations.yml +44 -0
  19. data/db/backups/.keep +0 -0
  20. data/db/migrate/001_create_projects.rb +22 -0
  21. data/db/migrate/002_create_exceptions.rb +14 -0
  22. data/db/migrate/003_add_meta_to_projects.rb +14 -0
  23. data/db/migrate/004_add_timestamps_to_projects.rb +12 -0
  24. data/db/migrate/005_create_classifiers.rb +19 -0
  25. data/lib/ossert/classifiers/decision_tree.rb +112 -0
  26. data/lib/ossert/classifiers/growing/check.rb +172 -0
  27. data/lib/ossert/classifiers/growing/classifier.rb +175 -0
  28. data/lib/ossert/classifiers/growing.rb +163 -0
  29. data/lib/ossert/classifiers.rb +14 -0
  30. data/lib/ossert/config.rb +24 -0
  31. data/lib/ossert/fetch/bestgems.rb +98 -0
  32. data/lib/ossert/fetch/github.rb +536 -0
  33. data/lib/ossert/fetch/rubygems.rb +80 -0
  34. data/lib/ossert/fetch.rb +142 -0
  35. data/lib/ossert/presenters/project.rb +202 -0
  36. data/lib/ossert/presenters/project_v2.rb +117 -0
  37. data/lib/ossert/presenters.rb +8 -0
  38. data/lib/ossert/project.rb +144 -0
  39. data/lib/ossert/quarters_store.rb +164 -0
  40. data/lib/ossert/rake_tasks.rb +6 -0
  41. data/lib/ossert/reference.rb +87 -0
  42. data/lib/ossert/repositories.rb +138 -0
  43. data/lib/ossert/saveable.rb +153 -0
  44. data/lib/ossert/stats/agility_quarter.rb +62 -0
  45. data/lib/ossert/stats/agility_total.rb +71 -0
  46. data/lib/ossert/stats/base.rb +113 -0
  47. data/lib/ossert/stats/community_quarter.rb +28 -0
  48. data/lib/ossert/stats/community_total.rb +24 -0
  49. data/lib/ossert/stats.rb +32 -0
  50. data/lib/ossert/tasks/database.rake +179 -0
  51. data/lib/ossert/tasks/ossert.rake +52 -0
  52. data/lib/ossert/version.rb +4 -0
  53. data/lib/ossert/workers/fetch.rb +21 -0
  54. data/lib/ossert/workers/fetch_bestgems_page.rb +32 -0
  55. data/lib/ossert/workers/refresh_fetch.rb +22 -0
  56. data/lib/ossert/workers/sync_rubygems.rb +0 -0
  57. data/lib/ossert/workers.rb +11 -0
  58. data/lib/ossert.rb +63 -0
  59. data/ossert.gemspec +47 -0
  60. metadata +396 -0
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Stats
4
+ class Base
5
+ class << self
6
+ attr_accessor :section, :section_type
7
+
8
+ def config
9
+ @config ||= ::Settings['stats'][section][section_type]
10
+ end
11
+
12
+ def attributes
13
+ @attributes ||= config['attributes']
14
+ end
15
+
16
+ def uniq_attributes
17
+ @uniq_attributes ||= config['uniq_attributes'].to_a
18
+ end
19
+
20
+ def absolute_attributes
21
+ @absolute_attributes ||= config['absolute_attributes'].to_a
22
+ end
23
+
24
+ def attributes_names
25
+ @attributes_names ||= attributes.keys
26
+ end
27
+
28
+ def metrics
29
+ @metrics ||= config['metrics']
30
+ end
31
+
32
+ def create_attributes_accessors
33
+ attr_accessor(*attributes_names)
34
+ end
35
+
36
+ def define_ints(*attributes)
37
+ Array.wrap(attributes).each do |metric|
38
+ define_method("#{metric}_int") { public_send(metric).to_i }
39
+ end
40
+ end
41
+
42
+ def define_counts(*attributes)
43
+ Array.wrap(attributes).each do |metric|
44
+ define_method("#{metric}_count") { public_send(metric).count }
45
+ end
46
+ end
47
+
48
+ def define_percent(attributes, default_value: 0)
49
+ attributes.to_h.each do |metric, total|
50
+ define_method("#{metric}_percent") do
51
+ total_count = public_send(total).count
52
+ return default_value if total_count.zero?
53
+
54
+ ((public_send(metric).count.to_d / total_count.to_d) * 100).round(2)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def <<(other_stats)
61
+ self.class.attributes_names.each do |attr|
62
+ next unless other_value = other_stats.instance_variable_get("@#{attr}")
63
+ new_value = other_value
64
+ new_value += instance_variable_get("@#{attr}") unless self.class.absolute_attributes.include?(attr)
65
+ new_value.uniq! if self.class.uniq_attributes.include?(attr)
66
+
67
+ instance_variable_set("@#{attr}", new_value)
68
+ end
69
+ self
70
+ end
71
+
72
+ def initialize
73
+ self.class.attributes.each do |var, type|
74
+ instance_variable_set("@#{var}", Kernel.const_get(type).new) if type
75
+ end
76
+ end
77
+
78
+ def median(values, default_value: 0)
79
+ values = Array(values).sort
80
+ return default_value if (count = values.count).zero?
81
+
82
+ middle_idx = values.count / 2
83
+ return values[middle_idx] if count.odd?
84
+
85
+ (values[middle_idx - 1] + values[middle_idx]) / 2
86
+ end
87
+
88
+ def metric_values
89
+ self.class.metrics.map { |metric| public_send(metric).to_f }
90
+ end
91
+
92
+ def metrics_to_hash
93
+ self.class.metrics.each_with_object({}) do |var, result|
94
+ value = send(var)
95
+ value.uniq! if self.class.uniq_attributes.include?(var)
96
+ result[var] = value
97
+ end
98
+ end
99
+
100
+ def to_hash
101
+ self.class.attributes_names.each_with_object({}) do |var, result|
102
+ value = send(var)
103
+ value.uniq! if self.class.uniq_attributes.include?(var)
104
+ result[var] = value
105
+ end
106
+ end
107
+
108
+ def to_json
109
+ MultiJson.dump(self)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Stats
4
+ class CommunityQuarter < Base
5
+ self.section = 'community'
6
+ self.section_type = 'quarter'
7
+ create_attributes_accessors
8
+
9
+ define_counts(
10
+ :users_creating_issues, :users_commenting_issues, :users_creating_pr,
11
+ :users_commenting_pr, :contributors, :stargazers, :forks,
12
+ :users_involved
13
+ )
14
+
15
+ def users_involved_no_stars_count
16
+ (users_involved - stargazers).count
17
+ end
18
+
19
+ def total_downloads_count
20
+ delta_downloads
21
+ end
22
+
23
+ def download_divergence
24
+ (delta_downloads.to_f / (1 + total_downloads.to_f)) * 100.0
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Stats
4
+ class CommunityTotal < Base
5
+ self.section = 'community'
6
+ self.section_type = 'total'
7
+ create_attributes_accessors
8
+
9
+ define_counts(
10
+ :users_creating_issues, :users_commenting_issues, :users_creating_pr,
11
+ :users_commenting_pr, :contributors, :watchers, :stargazers, :forks,
12
+ :users_involved, :dependants
13
+ )
14
+
15
+ def users_involved_no_stars_count
16
+ (users_involved - stargazers).count
17
+ end
18
+
19
+ def total_downloads_count
20
+ total_downloads
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require 'ossert/stats/base'
3
+ require 'ossert/stats/agility_total'
4
+ require 'ossert/stats/agility_quarter'
5
+ require 'ossert/stats/community_total'
6
+ require 'ossert/stats/community_quarter'
7
+
8
+ module Ossert
9
+ module Stats
10
+ PER_QUARTER_TOO_LONG = (5.years / 1.day).to_i
11
+ PER_YEAR_TOO_LONG = PER_QUARTER_TOO_LONG / 4
12
+
13
+ def guess_section_by_metric(metric)
14
+ found_section = :not_found
15
+ section_by_metric.each do |section, metrics|
16
+ next unless metrics.include? metric
17
+ found_section = section
18
+ break
19
+ end
20
+ found_section
21
+ end
22
+ module_function :guess_section_by_metric
23
+
24
+ def section_by_metric
25
+ @section_by_metric ||= {
26
+ agility: AgilityTotal.metrics + AgilityQuarter.metrics,
27
+ community: CommunityTotal.metrics + CommunityQuarter.metrics
28
+ }
29
+ end
30
+ module_function :section_by_metric
31
+ end
32
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+ namespace :db do
3
+ require 'sequel'
4
+ Sequel.extension :migration
5
+
6
+ namespace :test do
7
+ task :prepare do
8
+ test_database_url = ENV.fetch('TEST_DATABASE_URL')
9
+ database_name = test_database_url.split('/').last
10
+
11
+ DB = Sequel.connect(test_database_url)
12
+
13
+ sh "dropdb #{database_name}" do
14
+ # Ignore errors
15
+ end
16
+
17
+ sh "createdb #{database_name}" do
18
+ # Ignore errors
19
+ end
20
+
21
+ Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
22
+ Rake::Task['db:version'].execute
23
+ end
24
+ end
25
+
26
+ task :migrate => :load_config do
27
+ Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
28
+ Rake::Task['db:version'].execute
29
+ end
30
+
31
+ desc 'Prints current schema version'
32
+ task :version do
33
+ version = if DB.tables.include?(:schema_info)
34
+ DB[:schema_info].first[:version]
35
+ end || 0
36
+
37
+ puts "Schema Version: #{version}"
38
+ end
39
+
40
+ task :load_config do
41
+ DB = Sequel.connect(ENV.fetch('DATABASE_URL'))
42
+ end
43
+
44
+ desc 'Create the database, load the schema, and initialize with the seed data (db:reset to also drop the db first)'
45
+ task :setup do
46
+ Rake::Task['db:create'].invoke
47
+ Rake::Task['db:load_config'].invoke
48
+
49
+ Sequel::Migrator.run(DB, File.expand_path('../../../../db/migrate', __FILE__))
50
+ Rake::Task['db:version'].execute
51
+ end
52
+
53
+ task :drop do
54
+ sh "dropdb #{ENV.fetch('DATABASE_URL').split('/').last}" do
55
+ # Ignore errors
56
+ end
57
+ end
58
+
59
+ task :create do
60
+ sh "createdb #{ENV.fetch('DATABASE_URL').split('/').last}" do
61
+ # Ignore errors
62
+ end
63
+ end
64
+
65
+ desc 'Dumps the database to backups'
66
+ task :dump, [:fmt] do |_, args|
67
+ dump_fmt = args.fmt || 'c' # or 'p', 't', 'd'
68
+ dump_sfx = suffix_for_format dump_fmt
69
+ backup_dir = backup_directory true
70
+ cmd = nil
71
+ with_config do |app, db_url|
72
+ file_name = Time.now.strftime('%Y%m%d%H%M%S') + '_' + app + '_db.' + dump_sfx
73
+ cmd = "pg_dump #{db_url} --no-owner --no-acl -F #{dump_fmt} -v -f #{backup_dir}/#{file_name}"
74
+ end
75
+ puts cmd
76
+ sh cmd do
77
+ # Ignore errors
78
+ end
79
+ end
80
+
81
+ desc 'Show the existing database backups'
82
+ task :list_backups do
83
+ backup_dir = backup_directory
84
+ puts backup_dir.to_s
85
+ exec "/bin/ls -lht #{backup_dir}"
86
+ end
87
+
88
+ desc 'Restores the database from a backup using PATTERN'
89
+ task :restore, [:pat] do |_, args|
90
+ puts 'Please pass a pattern to the task' unless args.pat.present?
91
+ cmd = nil
92
+ with_config do |_, db_url|
93
+ cmd = command_for_files args.pat, db_url
94
+ end
95
+ unless cmd.nil?
96
+ Rake::Task['db:drop'].invoke
97
+ Rake::Task['db:create'].invoke
98
+ puts cmd
99
+ exec "#{cmd} || exit 0"
100
+ end
101
+ end
102
+
103
+ def command_for_files(pattern, db_url)
104
+ files = Dir.glob("#{backup_directory}/*#{pattern}*")
105
+ case files.size
106
+ when 0
107
+ puts "No backups found for the pattern '#{pattern}'"
108
+ when 1
109
+ command_for_file files.first, db_url
110
+ else
111
+ puts "Too many files match the pattern '#{pattern}': #{files.join("\n ")} "
112
+ puts 'Try a more specific pattern'
113
+ end
114
+ end
115
+
116
+ def command_for_file(file, db_url)
117
+ return puts("No recognized dump file suffix: #{file}") unless (fmt = format_for_file(file)).present?
118
+ "pg_restore -d '#{db_url}' -F #{fmt} -v -c #{file}"
119
+ end
120
+
121
+ namespace :restore do
122
+ desc 'Restores the database from latest backup'
123
+ task :last do
124
+ cmd = nil
125
+ with_config do |_, db_url|
126
+ file = Dir.glob("#{backup_directory}/*").max_by { |f| File.mtime(f) }
127
+ if file
128
+ fmt = format_for_file file
129
+ if fmt.nil?
130
+ puts "No recognized dump file suffix: #{file}"
131
+ else
132
+ cmd = "pg_restore -d '#{db_url}' -F #{fmt} -v #{file}"
133
+ end
134
+ else
135
+ puts 'No backups found'
136
+ end
137
+ end
138
+ unless cmd.nil?
139
+ Rake::Task['db:drop'].invoke
140
+ Rake::Task['db:create'].invoke
141
+ puts cmd
142
+ exec "#{cmd} || exit 0"
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def suffix_for_format(suffix)
150
+ case suffix
151
+ when 'c' then 'dump'
152
+ when 'p' then 'sql'
153
+ when 't' then 'tar'
154
+ when 'd' then 'dir'
155
+ end
156
+ end
157
+
158
+ def format_for_file(file)
159
+ case file
160
+ when /\.dump$/ then 'c'
161
+ when /\.sql$/ then 'p'
162
+ when /\.dir$/ then 'd'
163
+ when /\.tar$/ then 't'
164
+ end
165
+ end
166
+
167
+ def backup_directory(create = false)
168
+ backup_dir = 'db/backups'
169
+ if create && !Dir.exist?(backup_dir)
170
+ puts "Creating #{backup_dir} .."
171
+ FileUtils.mkdir_p(backup_dir)
172
+ end
173
+ backup_dir
174
+ end
175
+
176
+ def with_config
177
+ yield 'ossert', ENV.fetch('DATABASE_URL')
178
+ end
179
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ namespace :ossert do
3
+ namespace :cache do
4
+ desc 'Reset data cache'
5
+ task :reset do
6
+ ::Ossert.init
7
+ ::Classifier.dataset.delete
8
+ Ossert::Classifiers::Growing.new.train
9
+ true
10
+ end
11
+ end
12
+
13
+ desc 'Collect reference projects'
14
+ task :collect_referencies do
15
+ puts 'Run collecting process'
16
+ time = Benchmark.realtime do
17
+ ::Ossert.init
18
+ ::Project.db.transaction do
19
+ Ossert::Project.cleanup_referencies!
20
+ reference_projects = Ossert::Reference.prepare_projects!
21
+ Ossert::Reference.process_references(reference_projects)
22
+ end
23
+ end
24
+
25
+ puts "Collecting process finished in #{time.round(3)} sec."
26
+ end
27
+
28
+ desc 'Invoke data updates for stale projects'
29
+ task :refresh_data do
30
+ require './config/sidekiq.rb'
31
+ Ossert::Workers::RefreshFetch.perform_async
32
+ end
33
+
34
+ desc 'Add or replace project name exception'
35
+ task :exception, [:name, :github_name] do |_, args|
36
+ raise 'Arguments name and GitHub name expected' unless args.name.present? && args.github_name.present?
37
+ exceptions_repo = ExceptionsRepo.new(Ossert.rom)
38
+ if exceptions_repo[args.name]
39
+ exceptions_repo.update(
40
+ args.name,
41
+ name: args.name,
42
+ github_name: args.github_name
43
+ )
44
+ else
45
+ exceptions_repo.create(
46
+ name: args.name,
47
+ github_name: args.github_name
48
+ )
49
+ end
50
+ puts "Exception '#{args.name}' => '#{args.github_name}' saved!"
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Workers
4
+ class Fetch
5
+ include Sidekiq::Worker
6
+ include Process
7
+ sidekiq_options unique: :until_executed,
8
+ unique_expiration: 1.hour,
9
+ retry: 3
10
+
11
+ def perform(name, reference = Ossert::Saveable::UNUSED_REFERENCE)
12
+ puts "Fetching data for: '#{name}' (ref: #{reference})"
13
+ pid = fork do
14
+ Ossert.init
15
+ Ossert::Project.fetch_all(name, reference)
16
+ end
17
+ waitpid(pid)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Workers
4
+ class FetchBestgemsPage
5
+ include Sidekiq::Worker
6
+ include Process
7
+
8
+ sidekiq_options retry: 3,
9
+ unique: :until_executed
10
+
11
+ def perform(pages, type = :total)
12
+ pages = Array(pages)
13
+ bestgems_page_processor = Kernel.const_get(
14
+ "Ossert::Fetch::Bestgems#{type.to_s.capitalize}Stat"
15
+ )
16
+ pages.each do |page|
17
+ puts "Processing Bestgems page: '#{page}'"
18
+ bestgems_page_processor.process_page(page) do |_, _, gem_name|
19
+ puts "Processing project: '#{gem_name}'"
20
+ pid = fork do
21
+ Ossert.init
22
+ next(puts("Skipping project: '#{gem_name}'")) if Ossert::Project.exist?(gem_name)
23
+ Ossert::Project.fetch_all(gem_name)
24
+ sleep(10)
25
+ end
26
+ waitpid(pid)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Ossert
3
+ module Workers
4
+ class RefreshFetch
5
+ include Sidekiq::Worker
6
+ include Process
7
+ sidekiq_options unique: :until_executed,
8
+ unique_expiration: 1.hour,
9
+ retry: 3
10
+
11
+ def perform
12
+ pid = fork do
13
+ Ossert.init
14
+ ::Project.select(:name, :reference).where('updated_at < ?', 1.week.ago).paged_each do |project|
15
+ Ossert::Workers::Fetch.perform_async(project.name, project.reference)
16
+ end
17
+ end
18
+ waitpid(pid)
19
+ end
20
+ end
21
+ end
22
+ end
File without changes
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require 'sidekiq'
3
+ require 'sidekiq-unique-jobs'
4
+ require 'ossert/workers/fetch'
5
+ require 'ossert/workers/fetch_bestgems_page'
6
+ require 'ossert/workers/refresh_fetch'
7
+
8
+ module Ossert
9
+ module Workers
10
+ end
11
+ end
data/lib/ossert.rb ADDED
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require 'ossert/version'
3
+ require 'sequel'
4
+ require 'active_support/all' # remove later, we use only quarters and index_by here
5
+ require 'json'
6
+ require 'oj'
7
+
8
+ require 'ossert/config'
9
+ Ossert::Config.load(:stats, :classifiers, :translations, :descriptions)
10
+
11
+ require 'ossert/stats'
12
+ require 'ossert/quarters_store'
13
+ require 'ossert/saveable'
14
+ require 'ossert/presenters'
15
+ require 'ossert/project'
16
+ require 'ossert/fetch'
17
+ require 'ossert/reference'
18
+ require 'ossert/classifiers'
19
+ require 'ossert/workers'
20
+
21
+ module Ossert
22
+ def init(database_url = nil)
23
+ Sequel.connect(database_url || ENV.fetch('DATABASE_URL'))
24
+ require 'ossert/repositories'
25
+ end
26
+ module_function :init
27
+
28
+ def description(key)
29
+ descriptions.fetch(key.to_s, "Description for '#{key}' - not found")
30
+ end
31
+ alias descr description
32
+ module_function :descr
33
+
34
+ def descriptions
35
+ @descriptions ||= ::Settings['descriptions']
36
+ end
37
+ module_function :descriptions
38
+
39
+ def translate(key)
40
+ translations.fetch(key.to_s, "Translation for '#{key}' - not found")
41
+ end
42
+ alias t translate
43
+ module_function :t
44
+
45
+ def translations
46
+ @translations ||= ::Settings['translations']
47
+ end
48
+ module_function :translations
49
+
50
+ NO_GITHUB_NAME = '__unknown__'
51
+
52
+ class TooLong
53
+ def self.new
54
+ Ossert::Stats::PER_YEAR_TOO_LONG
55
+ end
56
+ end
57
+
58
+ class Zero
59
+ def self.new
60
+ 0
61
+ end
62
+ end
63
+ end
data/ossert.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'ossert/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'ossert'
9
+ spec.version = Ossert::VERSION
10
+ spec.authors = ['Sergey Dolganov']
11
+ spec.email = ['sclinede@gmail.com']
12
+
13
+ spec.summary = 'Write a short summary, because Rubygems requires one.'
14
+ spec.description = 'Write a longer description or delete this line.'
15
+ spec.homepage = 'https://github.com/sclinede/ossert'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_runtime_dependency 'octokit', '~> 4.0'
24
+
25
+ spec.add_runtime_dependency 'redis'
26
+ spec.add_runtime_dependency 'redis-namespace'
27
+ spec.add_runtime_dependency 'sidekiq'
28
+ spec.add_runtime_dependency 'sidekiq-unique-jobs'
29
+
30
+ spec.add_runtime_dependency 'activesupport' # TODO: remove dependency later
31
+ spec.add_runtime_dependency 'oj'
32
+ spec.add_runtime_dependency 'multi_json'
33
+ spec.add_runtime_dependency 'sequel'
34
+ spec.add_runtime_dependency 'pg'
35
+ spec.add_runtime_dependency 'nokogiri'
36
+ spec.add_runtime_dependency 'graphr'
37
+ spec.add_runtime_dependency 'rake', '~> 10.0'
38
+
39
+ spec.add_development_dependency 'memory_profiler'
40
+ spec.add_development_dependency 'rubocop'
41
+ spec.add_development_dependency 'bundler', '~> 1.11'
42
+ spec.add_development_dependency 'pry'
43
+ spec.add_development_dependency 'rspec', '~> 3.0'
44
+ spec.add_development_dependency 'simplecov'
45
+ spec.add_development_dependency 'webmock'
46
+ spec.add_development_dependency 'vcr'
47
+ end