forked from zhon/combiner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsession_reviews.rb
234 lines (190 loc) · 5.83 KB
/
session_reviews.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/usr/bin/env ruby
# Copyright (c) 2009, Zhon Johansen
# All Rights Reserved.
#
# This is free software, covered under either the Ruby's license or the
# LGPL. See the COPYRIGHT file for more information.
#
# usage:
# ruby session_reviews.rb --help
#
require 'optparse'
require 'yaml'
class ReviewAssigner
def initialize(reviewers, sessions, eliminated_sessions)
@reviewers = reviewers
@sessions = sessions
@eliminated_sessions = eliminated_sessions
@matchups = ReviewerMatchups.new(reviewers)
end
def assign_reviewers
session_reviewers = {}
@sessions.each do |key, value|
next if @eliminated_sessions.include?(key)
reviewers = @matchups.next
while conflict? reviewers, key
reviewers = @matchups.next_previous_rejected
end
session_reviewers[key] = reviewers
end
return session_reviewers
end
def conflict? reviewers, session
not (reviewers & @sessions[session]).empty?
end
end
class ReviewerMatchups
def initialize(reviewers, group_size = 2)
@reviewers = reviewers
@group_size = group_size
@size = @reviewers.size
# TODO group_size must be a positive int
@reviewer_indexes = []
(0..(group_size-1)).each do |i|
#@reviewer_indexes.push((@size * i.to_f / group_size).to_i)
@reviewer_indexes.push(i)
end
@previous = nil
@stack = []
end
def next
unless @stack.empty?
@previous = @stack.pop
return @previous
end
_next
end
def next_previous_rejected
@stack.push @previous
_next
end
def _next
@previous = []
@reviewer_indexes.each do |i|
@previous.push @reviewers[i]
end
increment_indexes
@previous.uniq!
return _next unless @previous.size == @group_size
return @previous
end
def increment_indexes
if @reviewer_indexes[0] == @size-1
(1..@group_size-1).each do |i|
@reviewer_indexes[i] += i
end
end
(0..@group_size-1).each do |i|
@reviewer_indexes[i] = (1 + @reviewer_indexes[i]) % @size
end
increment_indexes unless all_indexes_increment?(@reviewer_indexes)
end
def all_indexes_increment?(indexes)
indexes.inject(0) {|l,i| return false if l >= i; i}
true
end
end
class CommandLine
def initialize(session_reviewers, args=ARGV)
@args = args
@session_reviewers = session_reviewers
@reviewer_sessions = true
parse
show_author_reviews unless @reviewer_sessions == false
end
def usage(usage, error_message=nil)
output = ''
output << "\n#{error_message}" if error_message
output << "\n#{usage}\n"
puts output
exit(error_message.nil? ? -1 : 0)
end
def parse
opts = OptionParser.new do |opts|
script_name = File.basename($0)
opts.banner = <<-end_usage
Match up a list of sessions with a list of reviewers
usage: ruby #{script_name} [options]
Create three input YAML files in the current directory
- "sessions.yaml"
- "reviewers.yaml"
- "eliminated.yaml"
The program will match up a session id with a number of reviewers (default 2). The "sessions.yaml" has session IDs followed by the author(s) and looks like:
:sessions: {
91: [Ron Jeffries, Chet Hendrickson],
111: [Kent Beck],
113: [Alistair Cockburn],
2941: [Ward Chunningham, Martin Fowler]
}
The "reviewers.yaml" has a list of reviewers names and looks like:
:reviewers: [
Ken Shwaber,
Jeff Sutherland,
James Grenning,
Jim Highsmith
]
The "eliminated.yaml" file contains a list of sessions that will not be assigned and looks like:
:eliminated_sessions: [
242,
3059, 2257, 1673, 747, 542, 283
]
OPTIONS
end_usage
opts.on('-n', '--reviewers SIZE',
'Number or reviewers per session(default 2)'){|n|@group_size=n.to_i}
opts.on('-r', '--session-reviewers',
'Show session with its reviewers') { show_session_reviewers }
opts.on('--[no-]reviewer-sessions',
'Show reviewer with his/her sessions') { |@reviewer_sessions| }
opts.separator ''
opts.on('--version', "Output version information and exit") do
puts <<-EOF
#{script_name} 0.1
Copyright (c) 2009 Zhon Johansen
License
Ruby <http://www.ruby-lang.org/en/LICENSE.txt> or
GPLv3 <http://www.gnu.org/licenses/gpl.html>
EOF
exit
end
opts.on('-h', '--help',
'Show this help message.') { usage(opts) }
begin
@start_dirs = opts.parse! ARGV
rescue OptionParser::ParseError => e
usage(opts, "Error: " + e.to_s.strip)
end
end
end
def show_session_reviewers
@session_reviewers.sort.each do |key, value|
printf("%-4d %-21s %-21s\n", key, value[0], value[1])
end
end
def show_author_reviews
author_reviews=convert_session_reviewers_to_author_reviews @session_reviewers
author_reviews.sort.each do |key, value|
printf("%-21s #{"%-5d" * value.size}\n", key, *value)
end
end
def convert_session_reviewers_to_author_reviews(session_reviewers)
author_reviews = {}
session_reviewers.each do |key,value|
value.each do |item|
author_reviews[item] ||= []
author_reviews[item].push(key)
end
end
author_reviews
end
end
if $0 == __FILE__
sessions = YAML.load(File.open('sessions.yaml'))
reviewers = YAML.load(File.open('reviewers.yaml'))
eliminated = YAML.load(File.open('eliminated.yaml'))
assigner = ReviewAssigner.new(reviewers[:reviewers],
sessions[:sessions],
eliminated[:eliminated_sessions])
session_reviewers = assigner.assign_reviewers
CommandLine.new(session_reviewers)
end