viking-pairity 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.byebug_history +179 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Guardfile +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/pairity +11 -0
- data/lib/pairity.rb +15 -0
- data/lib/pairity/adjacency_matrix.rb +142 -0
- data/lib/pairity/cli.rb +336 -0
- data/lib/pairity/config.rb +69 -0
- data/lib/pairity/edge.rb +17 -0
- data/lib/pairity/google_sync.rb +174 -0
- data/lib/pairity/pair_generator.rb +79 -0
- data/lib/pairity/pair_saver.rb +20 -0
- data/lib/pairity/pair_stats.rb +12 -0
- data/lib/pairity/person.rb +22 -0
- data/lib/pairity/slackbot.rb +28 -0
- data/lib/pairity/version.rb +3 -0
- data/pairity.gemspec +45 -0
- data/tags +121 -0
- data/test.rb +11 -0
- data/todays_pairs.txt +7 -0
- metadata +271 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Pairity
|
4
|
+
class Config
|
5
|
+
|
6
|
+
FILENAME = '.pairity'
|
7
|
+
PATH = "#{Dir.home}/#{FILENAME}"
|
8
|
+
|
9
|
+
@@config = {}
|
10
|
+
|
11
|
+
def self.config
|
12
|
+
@@config
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configured?
|
16
|
+
self.load
|
17
|
+
!@@config.any? { |key, value| value.nil? || value.empty? }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.add(options={})
|
21
|
+
add_option(:channel, options[:channel])
|
22
|
+
add_option(:url, options[:url])
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.save
|
26
|
+
File.open(PATH, 'w+') do |f|
|
27
|
+
f.write(YAML.dump(config))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.load
|
32
|
+
file_exists = File.exists?(PATH)
|
33
|
+
if File.exists?(PATH)
|
34
|
+
value = YAML.load_file(PATH)
|
35
|
+
else
|
36
|
+
value = defaults
|
37
|
+
end
|
38
|
+
@@config = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get(key)
|
42
|
+
config[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.display
|
46
|
+
if config.empty?
|
47
|
+
puts "No config values set"
|
48
|
+
else
|
49
|
+
config.each do |key, value|
|
50
|
+
puts "#{key.upcase}: #{value}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def self.defaults
|
58
|
+
{
|
59
|
+
:channel => 'general',
|
60
|
+
:url => '',
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.add_option(key, value)
|
65
|
+
config[key] = value unless value.nil?
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/pairity/edge.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Pairity
|
2
|
+
|
3
|
+
class Edge
|
4
|
+
attr_accessor :weight, :days, :resistance
|
5
|
+
|
6
|
+
def initialize(weight: 0, days: 0, resistance: 1)
|
7
|
+
@weight = weight
|
8
|
+
@days = days
|
9
|
+
@resistance = resistance
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"Weight: #{weight} Days: #{days}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require "google/api_client"
|
2
|
+
require 'pp'
|
3
|
+
require "google_drive"
|
4
|
+
require "ruby-progressbar"
|
5
|
+
|
6
|
+
ENV['SSL_CERT_FILE'] = Gem.loaded_specs['google-api-client'].full_gem_path+'/lib/cacerts.pem'
|
7
|
+
|
8
|
+
module Pairity
|
9
|
+
class GoogleSync
|
10
|
+
|
11
|
+
CONFIG_FILE = Dir.home + "/.pairity_google.json"
|
12
|
+
|
13
|
+
attr_reader :sheet_url
|
14
|
+
|
15
|
+
def initialize(matrix)
|
16
|
+
@matrix = matrix
|
17
|
+
@people = {}
|
18
|
+
@sheet_url = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def load
|
22
|
+
unless File.exists?(CONFIG_FILE)
|
23
|
+
puts "Welcome, newcomer!"
|
24
|
+
puts "Please follow these instructions to allow #{Rainbow("Pairity").white} to use Google Sheets to sync its precious data."
|
25
|
+
end
|
26
|
+
|
27
|
+
session = GoogleDrive.saved_session(CONFIG_FILE)
|
28
|
+
|
29
|
+
puts "Loading Matrix from Google Sheets..."
|
30
|
+
progressbar = ProgressBar.create(total: 100)
|
31
|
+
|
32
|
+
progressbar.progress += 20
|
33
|
+
sheet = session.spreadsheet_by_title("Pairity")
|
34
|
+
unless sheet
|
35
|
+
puts "Creating a new spreadsheet called: Pairity"
|
36
|
+
sheet = session.create_spreadsheet("Pairity")
|
37
|
+
sheet.add_worksheet("Days")
|
38
|
+
sheet.add_worksheet("Resistance")
|
39
|
+
sheet.add_worksheet("Weights")
|
40
|
+
ws = sheet.worksheets[0]
|
41
|
+
ws.title = "People"
|
42
|
+
ws.save
|
43
|
+
else
|
44
|
+
end
|
45
|
+
@sheet_url = sheet.worksheets[0].human_url
|
46
|
+
|
47
|
+
ws = sheet.worksheets[0]
|
48
|
+
|
49
|
+
# Add People and Tiers to Matrix
|
50
|
+
ws.num_rows.times do |row|
|
51
|
+
next unless row > 0
|
52
|
+
name = ws[row + 1, 1]
|
53
|
+
next if name.strip.empty?
|
54
|
+
tier = ws[row + 1, 2]
|
55
|
+
if name == "Han Solo"
|
56
|
+
person = @matrix.han_solo
|
57
|
+
else
|
58
|
+
person = Person.new(name: name, tier: tier)
|
59
|
+
@matrix.add_person(person)
|
60
|
+
end
|
61
|
+
@people[person] = row + 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add data to edges
|
65
|
+
(1..3).each do |i|
|
66
|
+
ws = sheet.worksheets[i]
|
67
|
+
@people.each do |p1, row|
|
68
|
+
@people.each do |p2, col|
|
69
|
+
next if p1 == p2
|
70
|
+
data = ws[row, col]
|
71
|
+
edit_edge(p1, p2, data, i)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
@matrix
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def clear_worksheet(ws)
|
81
|
+
(1..ws.num_rows).each do |i|
|
82
|
+
(1..ws.num_rows).each do |j|
|
83
|
+
ws[i, j] = ""
|
84
|
+
ws[j, i] = ""
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def save
|
90
|
+
puts "Saving Matrix to Google Sheets..."
|
91
|
+
progressbar = ProgressBar.create(total: 100)
|
92
|
+
|
93
|
+
session = GoogleDrive.saved_session(CONFIG_FILE)
|
94
|
+
sheet = session.spreadsheet_by_title("Pairity")
|
95
|
+
@people = @matrix.all_people.sort
|
96
|
+
p @matrix.matrix
|
97
|
+
|
98
|
+
progressbar.progress += 20
|
99
|
+
|
100
|
+
ws = sheet.worksheets[0]
|
101
|
+
|
102
|
+
clear_worksheet(ws)
|
103
|
+
|
104
|
+
ws[1, 1] = "Name"
|
105
|
+
ws[1, 2] = "Tier (1-3)"
|
106
|
+
@people.each_with_index do |person, index|
|
107
|
+
ws[index + 2, 1] = person.name
|
108
|
+
ws[index + 2, 2] = person.tier
|
109
|
+
end
|
110
|
+
ws.save
|
111
|
+
|
112
|
+
(1..3).each do |i|
|
113
|
+
ws = sheet.worksheets[i]
|
114
|
+
|
115
|
+
clear_worksheet(ws)
|
116
|
+
|
117
|
+
@people.each_with_index do |person, index|
|
118
|
+
ws[1, index + 2] = person.name
|
119
|
+
ws[index + 2, 1] = person.name
|
120
|
+
end
|
121
|
+
|
122
|
+
progressbar.progress += 20
|
123
|
+
@people.combination(2) do |pair|
|
124
|
+
p1, p2 = pair
|
125
|
+
index1 = @people.index(p1)
|
126
|
+
index2 = @people.index(p2)
|
127
|
+
edge = @matrix[p1,p2]
|
128
|
+
case i
|
129
|
+
when 1
|
130
|
+
ws[1,1] = "Days"
|
131
|
+
data = edge.days
|
132
|
+
when 2
|
133
|
+
ws[1,1] = "Resistances"
|
134
|
+
data = edge.resistance
|
135
|
+
when 3
|
136
|
+
ws[1,1] = "Weights"
|
137
|
+
data = edge.weight
|
138
|
+
end
|
139
|
+
ws[index1 + 2, index2 + 2] = data
|
140
|
+
ws[index2 + 2, index1 + 2] = data
|
141
|
+
end
|
142
|
+
|
143
|
+
max = ws.max_rows
|
144
|
+
|
145
|
+
@people.each_with_index do |person, index|
|
146
|
+
ws[index + 2, index + 2] = ""
|
147
|
+
ws[index + 2, index + 2] = ""
|
148
|
+
max = index + 3
|
149
|
+
end
|
150
|
+
|
151
|
+
ws.save
|
152
|
+
end
|
153
|
+
progressbar.progress += 20
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
def edit_edge(p1, p2, data, i)
|
158
|
+
case i
|
159
|
+
when 1
|
160
|
+
@matrix[p1, p2].days = data.to_i
|
161
|
+
when 2
|
162
|
+
@matrix[p1, p2].resistance = (data ? 1 : data.to_i)
|
163
|
+
when 3
|
164
|
+
@matrix[p1, p2].weight = data.to_i
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def find_person(name)
|
169
|
+
|
170
|
+
person = @people.find { |person| person.name == name }
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
require 'pry-byebug'
|
3
|
+
|
4
|
+
module Pairity
|
5
|
+
class PairGenerator
|
6
|
+
|
7
|
+
@@shuffle = false
|
8
|
+
|
9
|
+
attr_reader :pairs, :last_pairs
|
10
|
+
|
11
|
+
def self.shuffle=(toggle)
|
12
|
+
@@shuffle = toggle
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(matrix)
|
16
|
+
@matrix = matrix
|
17
|
+
@last_pairs = []
|
18
|
+
@pairs = []
|
19
|
+
@nopes = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_pairs
|
23
|
+
@pairs = []
|
24
|
+
@pairs = @matrix.optimal_pairs(@nopes)
|
25
|
+
move_solo_to_the_end
|
26
|
+
@pairs
|
27
|
+
end
|
28
|
+
|
29
|
+
def possible_pairs
|
30
|
+
@matrix.possible_pairs.reject { |pairing| pairing.any? { |pair| @nopes.include?(pair) }}
|
31
|
+
end
|
32
|
+
|
33
|
+
def move_solo_to_the_end
|
34
|
+
solo = @pairs.find { |pair| pair.any? { |person| person.name == "Han Solo" } }
|
35
|
+
return @pairs unless solo
|
36
|
+
index = @pairs.index(solo)
|
37
|
+
@pairs[index], @pairs[-1] = @pairs[-1], @pairs[index]
|
38
|
+
@pairs
|
39
|
+
end
|
40
|
+
|
41
|
+
def nope(p1,p2)
|
42
|
+
pair = [p1, p2].sort
|
43
|
+
@nopes << pair
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_pairs
|
47
|
+
@pairs.each do |pair|
|
48
|
+
@matrix.add_weight_to_pair(1, pair)
|
49
|
+
@matrix.add_day_to_pair(pair)
|
50
|
+
end
|
51
|
+
@nopes = []
|
52
|
+
@last_pairs = @pairs
|
53
|
+
end
|
54
|
+
|
55
|
+
def revert
|
56
|
+
@pairs = @last_pairs
|
57
|
+
end
|
58
|
+
|
59
|
+
def days_for_pair(person1, person2)
|
60
|
+
@matrix[person1, person2].days
|
61
|
+
end
|
62
|
+
|
63
|
+
def abolish_pairing(person1, person2)
|
64
|
+
@matrix.add_weight_to_pair(1_000_000,[person1,person2].sort)
|
65
|
+
end
|
66
|
+
|
67
|
+
def resistance(person1, person2, resistance)
|
68
|
+
@matrix[person1, person2].resistance = resistance
|
69
|
+
end
|
70
|
+
|
71
|
+
def equilibrium
|
72
|
+
@matrix.people.combination(2).to_a.size / (@matrix.people.size / 2)
|
73
|
+
end
|
74
|
+
|
75
|
+
def include?(person1, person2)
|
76
|
+
@pairs.any? { |pair| pair == [person1, person2].sort }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pairity
|
2
|
+
class PairSaver
|
3
|
+
def initialize(pairs)
|
4
|
+
@pairs = pairs
|
5
|
+
end
|
6
|
+
|
7
|
+
def post_to_slack
|
8
|
+
rows = []
|
9
|
+
@pairs.each_with_index do |pair, index|
|
10
|
+
col = []
|
11
|
+
col << "Room ##{index+1}"
|
12
|
+
col << pair[0]
|
13
|
+
col << pair[1]
|
14
|
+
rows << col
|
15
|
+
end
|
16
|
+
table = Terminal::Table.new headings: ["Room","Driver","Navigator"], rows: rows
|
17
|
+
Slackbot.new.post("```#{table.to_s}```")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Pairity
|
2
|
+
class Person
|
3
|
+
@@id = 0
|
4
|
+
|
5
|
+
attr_accessor :name, :tier, :id
|
6
|
+
|
7
|
+
def initialize(name:, tier: 2)
|
8
|
+
@name = name
|
9
|
+
@tier = !((1..3) === tier) ? 2 : tier
|
10
|
+
end
|
11
|
+
|
12
|
+
def <=>(other_person)
|
13
|
+
return 1 if name == "Han Solo"
|
14
|
+
return -1 if other_person.name == "Han Solo"
|
15
|
+
name <=> other_person.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# require 'slack-poster'
|
2
|
+
|
3
|
+
module Pairity
|
4
|
+
class Slackbot
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
Config.load
|
8
|
+
hook = Config.get(:url)
|
9
|
+
options = {
|
10
|
+
icon_url: 'https://static1.squarespace.com/static/54e22d6be4b00617871820ca/54e567bbe4b022f3194c63b5/55ddd86ae4b0f0c3127f8483/1440605037480/?format=1000w',
|
11
|
+
username: 'Pairity',
|
12
|
+
channel: slack_channel
|
13
|
+
}
|
14
|
+
# @poster = Slack::Poster.new(hook, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post(message)
|
18
|
+
# @poster.send_message(message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def slack_channel
|
22
|
+
channel = Config.get(:channel)
|
23
|
+
return "#" + channel unless channel =~ /^#/
|
24
|
+
channel
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/pairity.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pairity/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "viking-pairity"
|
8
|
+
spec.version = Pairity::VERSION
|
9
|
+
spec.authors = ["Kit Langton"]
|
10
|
+
spec.email = ["kitlangton@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A fair and balanced gem for pair rotation.}
|
13
|
+
spec.description = %q{A fair and balanced gem for pair rotation.}
|
14
|
+
spec.homepage = "https://github.com/kitlangton/pairity"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
spec.add_development_dependency "guard-rspec"
|
34
|
+
spec.add_development_dependency "byebug"
|
35
|
+
spec.add_development_dependency "pry-byebug"
|
36
|
+
|
37
|
+
spec.add_runtime_dependency "highline"
|
38
|
+
spec.add_runtime_dependency "rainbow"
|
39
|
+
spec.add_runtime_dependency "google_drive"
|
40
|
+
spec.add_runtime_dependency "google-api-client", "~> 0.8.6"
|
41
|
+
spec.add_runtime_dependency "terminal-table"
|
42
|
+
spec.add_runtime_dependency "ruby-progressbar"
|
43
|
+
spec.add_runtime_dependency "slack-poster"
|
44
|
+
spec.add_runtime_dependency "graph_matching"
|
45
|
+
end
|