developer_cliques 0.5.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7c3671bced3ec1f9c2dad3c41542e37fa78378235e0a0e5edc452e7f82449da9
4
+ data.tar.gz: 710b910a669260612d682d4af97422e19b5a5543cc0ced7b332fde72588afdb7
5
+ SHA512:
6
+ metadata.gz: 563afcf91b96c60d415699e3848a85a8354594258ea3e841f48093c66978be235be8bae77fe780a9a82e87c6335b2487a2740bbb4ac0e916abf4bd347f4c2aa6
7
+ data.tar.gz: 00bd75cf2688dfe664aff807502067f79b64c6ee217c462f63113e043196c4378d325d77b244ce235451c6ad0ddcf38bf602b92cc629d174f494dad9807ef1b7
data/bin/devcliques ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'dotenv'
5
+ require_relative '../lib/developer_cliques'
6
+
7
+ Dotenv.load('.env')
8
+
9
+ begin
10
+ opt_parser = OptionParser.new do |opts|
11
+ opts.banner = "Usage: developer_cliques.rb [options]"
12
+
13
+ opts.on("-f", "--file USERNAMES", "Calculates maximal cliques") do |file|
14
+ DeveloperCliques.new(file: file).max_cliques.each { |clique| puts clique.join(' ') }
15
+ end
16
+
17
+ opts.on_tail("-e", "--env ENVIRONMENT_VARS", "Calculates maximal cliques") do |env|
18
+ Dotenv.load(env) if env
19
+ end
20
+
21
+ opts.on_tail("-h", "--help", "Show this help") do
22
+ puts opts
23
+ exit
24
+ end
25
+ end
26
+
27
+ opt_parser.parse!
28
+
29
+ rescue OptionParser::MissingArgument => error
30
+ puts error
31
+ puts opt_parser
32
+ end
@@ -0,0 +1,22 @@
1
+
2
+ require 'logger'
3
+
4
+ class AppLogger
5
+
6
+ LEVELS = ["info", "warn", "debug", "error", "fatal"]
7
+
8
+ class << self
9
+
10
+ def logger
11
+ Logger.new('application.log', 'monthly')
12
+ end
13
+
14
+ end
15
+
16
+ LEVELS.each do |level|
17
+ define_singleton_method level.to_sym do |message|
18
+ logger.send level.to_sym, message
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,7 @@
1
+ require 'octokit'
2
+
3
+ class GithubClient
4
+ def self.get
5
+ Octokit::Client.new(access_token: ENV["GITHUB_ACCESS_TOKEN"])
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require 'twitter'
2
+
3
+ class TwitterClient
4
+ def self.get
5
+ Twitter::REST::Client.new do |config|
6
+ config.consumer_key = ENV["TWITTER_CONSUMER_KEY"]
7
+ config.consumer_secret = ENV["TWITTER_CONSUMER_SECRET"]
8
+ config.access_token = ENV["TWITTER_ACCESS_TOKEN"]
9
+ config.access_token_secret = ENV["TWITTER_ACCESS_TOKEN_SECRET"]
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,82 @@
1
+
2
+ require 'twitter'
3
+ require 'octokit'
4
+ require_relative '../../config/app_logger'
5
+
6
+ class ConnectedDevelopers
7
+
8
+ attr_reader :developers
9
+
10
+ def initialize developers:, twitter_client:, github_client:
11
+ @twitter_client = twitter_client
12
+ @github_client = github_client
13
+ @developers = developers
14
+ @organizations = {}
15
+ end
16
+
17
+ def graph
18
+ @graph ||= generate_graph
19
+ end
20
+
21
+ def friends user_name
22
+ friends_list = twitter_retry { @twitter_client.friends(user_name).entries.map{ |u| u.screen_name } }
23
+ AppLogger.debug "FRIENDS: #{friends_list}"
24
+ friends_list
25
+ end
26
+
27
+ def followers user_name
28
+ follow_list = twitter_retry { @twitter_client.followers(user_name).entries.map{ |u| u.screen_name } }
29
+ AppLogger.debug "FRIENDS: #{follow_list}"
30
+ follow_list
31
+ end
32
+
33
+ def organizations user_name
34
+ @organizations[user_name] ||= @github_client.organizations(user_name).map{ |u| u.login }
35
+ end
36
+
37
+ private
38
+
39
+ def generate_graph
40
+ developers_graph = {}
41
+ excluded = []
42
+
43
+ @developers.each do |developer|
44
+ AppLogger.debug "GITHUB USER: #{developer}"
45
+ AppLogger.debug "ORGANIZATIONS: #{organizations(developer)}"
46
+
47
+ excluded << developer
48
+ developers_graph[developer] ||= []
49
+
50
+ #Bidireccional relationship. It excludes iterated developers and itself. We look in one way only
51
+ (@developers-excluded).each do |developer2|
52
+ # Intersection between organizations if there are some organization in common goes in
53
+ unless (organizations(developer) & organizations(developer2)).empty?
54
+ #It creates relationship in one direction
55
+ developers_graph[developer] << developer2
56
+ # And reverse direction
57
+ developers_graph[developer2] ||= []
58
+ developers_graph[developer2] << developer
59
+ end
60
+ end
61
+
62
+ #Intersection between friends, followers and current relationships by organizations in common
63
+ developers_graph[developer] &= friends(developer) & followers(developer) unless developers_graph[developer].empty?
64
+
65
+ end
66
+
67
+ developers_graph
68
+ end
69
+
70
+ def twitter_retry &block
71
+ begin
72
+ yield
73
+ rescue Twitter::Error::TooManyRequests => error
74
+ # NOTE: Your process could go to sleep for up to 15 minutes but if you
75
+ # retry any sooner, it will almost certainly fail with the same exception.
76
+ AppLogger.debug "TOO MANY REQUEST: #{error}. Must wait #{error.rate_limit.reset_in/60} min"
77
+ sleep error.rate_limit.reset_in + 1
78
+ retry
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,45 @@
1
+
2
+ class Graph
3
+
4
+ attr_reader :edges, :nodes
5
+
6
+ def initialize edges: {}
7
+ @edges = edges
8
+ @nodes = edges.keys
9
+ end
10
+
11
+ def neighbours node:
12
+ @edges[node] || []
13
+ end
14
+
15
+ def max_cliques
16
+ @cliques ||= []
17
+ bron_kerbosch(possibles: nodes) if @cliques.empty?
18
+ @cliques
19
+ end
20
+
21
+ private
22
+
23
+ # Bron-Kerbosch Algorithm with Pivoting
24
+ # @param result
25
+ # is the set with the nodes of a maximal clique
26
+ # @param possibles
27
+ # is the set of the possible nodes to look
28
+ # @param excluded
29
+ # is the set of the excluded nodes
30
+ def bron_kerbosch result: [], possibles: [], excluded: []
31
+ @cliques << result if possibles.empty? and excluded.empty?
32
+
33
+ #Pivoting: it takes first element of possibles as pivot
34
+ (possibles - neighbours(node: possibles.first)).each do |n|
35
+ # Recursive call
36
+ bron_kerbosch result: result | [n],
37
+ possibles: possibles & neighbours(node: n),
38
+ excluded: excluded & neighbours(node: n)
39
+
40
+ possibles -= [n]
41
+ excluded |= [n]
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require_relative 'developer_cliques/connected_developers'
3
+ require_relative 'developer_cliques/graph'
4
+ require_relative '../config/twitter_config'
5
+ require_relative '../config/github_config'
6
+
7
+
8
+ class DeveloperCliques
9
+
10
+ def initialize file:
11
+ @file = file
12
+ end
13
+
14
+ def developers
15
+ @developers ||= read_file
16
+ end
17
+
18
+ def max_cliques
19
+ developers_graph = Graph.new edges: connected_developers.graph
20
+ developers_graph.max_cliques
21
+ end
22
+
23
+ def connected_developers
24
+ ConnectedDevelopers.new developers: developers,
25
+ twitter_client: TwitterClient.get,
26
+ github_client: GithubClient.get
27
+ end
28
+
29
+ private
30
+
31
+ def read_file
32
+ File.readlines(@file).map{ |line| line.strip }
33
+ end
34
+
35
+ end
36
+
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: developer_cliques
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Adrián Cepillo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Calculates maximal cliques from developers social connections
14
+ email: adrian.cepillo@gmail.com
15
+ executables:
16
+ - devcliques
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/devcliques
21
+ - config/app_logger.rb
22
+ - config/github_config.rb
23
+ - config/twitter_config.rb
24
+ - lib/developer_cliques.rb
25
+ - lib/developer_cliques/connected_developers.rb
26
+ - lib/developer_cliques/graph.rb
27
+ homepage: http://rubygems.org/gems/developer_cliques
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.7.7
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: Calculates maximal cliques from developers social connections
51
+ test_files: []