stefon 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OWNhNzAwM2UyYWVmNDA5NGE4MTEwZjAyMTYwODFmOGM1MWEwZjFlOQ==
5
+ data.tar.gz: !binary |-
6
+ NTJmYzhhZmE5NGQxYjllMmY0ZmQzM2U3OTMzOGM0ZTBjMGQ3MjlkNA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NjZmNTgyNzY0ZGZjMjRlYmJiZTA5ODk1ODAxMTk4MTI3ZmVlMWY0N2I1OTAz
10
+ M2Y2NDg1OTNjNDM3MTM2YjlkZDYzYTgzZWU5NzdmNmRiZjFhOTAxOWJjMjBl
11
+ MDc0MGY3OTNjN2FmOWNhZWE3ZGM4MzgwZmZhMzM2OThjNTFhNWE=
12
+ data.tar.gz: !binary |-
13
+ NGE4MGQ4NWU1ODA2ZTQxOTExNzZhNjIxYTM0MTY3YTI2ODAyNGNiZjQwZDk2
14
+ ZDY4OWNhYWNkNDJiZDY4NGQ2ZTNkYTBjMWQyNDY0MTc0MTcxZTYxYmExYmUy
15
+ YTJmZjJlNGZkMzA3YTQ4NmMyMWUxOTE0ZTY3MjRlNTE1NzQxOTA=
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ilya Kavalerov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Stefon
2
+
3
+ Get tips on who to ask for a code review.
4
+
5
+ A name inspired by the SNL character Bill Hader played:
6
+ ![stefon on wikipedia](http://upload.wikimedia.org/wikipedia/en/c/c1/Stefon%2C_SNL_Character.jpg)
7
+
8
+ SNL Stefon always gave advice on the best places to go to in the city. Stefon the gem will do the same: he tells you who to go to for a code review (or send a PR to) based on whose code you changed most.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'stefon'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install stefon
23
+
24
+ ## Usage
25
+ Execute:
26
+
27
+ $ stefon
28
+
29
+ To get the top 4 people to send a code review to.
30
+
31
+ You can use the -l option to change the number of people Stefon's recommendations are limited to:
32
+ Execute:
33
+
34
+ $ stefon --limit 42
35
+
36
+ To get the top 42 people to ask for a code review (identical to `stefon -l 42` ).
37
+
38
+ To get a detailed report of code that you changed, execute:
39
+
40
+ $ stefon --full-report
41
+ which is identical to `stefon -f`, to see something like:
42
+
43
+ $ stefon -f
44
+ Finished in 0.592418 seconds
45
+ Added 1 line to files written by: Cindy Sherman
46
+ Deleted 2 lines written by: Cindy Sherman
47
+ Added 1 line to files written by: Ed Ruscha
48
+ Deleted 1 line written by: Ed Ruscha
49
+ Deleted 1 file written by: Ed Ruscha
50
+ Deleted 1 line written by: Gerhard Richter
51
+ The top commiter in this repo is: Van Gogh
52
+
53
+
54
+ ## Todos / Comming improvements
55
+ * give identity of predominant author of a file you have deleted
56
+ * use Github API to get a list of contributors for a project (and filter suggestions)
57
+ * loading user preferences (list of users to exclude) - maybe with dotfiles instead of yml files
58
+ * handle errors from surveryors
59
+ * remove dependency of Grit class on Git module
60
+ * exclude authors from
61
+ * the grit module - file top author
62
+ * the git module - repo top commiter
63
+
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create new Pull Request
72
+
73
+ ### The narrative of stefon's code
74
+
75
+ Stefon lives in the shell. We ask for suggestions by interacting with the `CLI` (using the trollop gem for getting cmd line options), the `CLI` gets an `Editor` to write a script for Stefon. The `Editor` has a team of `Surveyors` that look for certain changes in the codebase (`AddedLines`, `DeletedFiles`, etc.). To detect these changes, the `Surveyors` use `GitUtils` (a wrapper for cmd line git interface) and `GritUtils` (a wrapper for another gem called grit), and store scores in a `SurveyorStor`, which acts like a `Hash`. The `Editor` is responsible for assembling the team, telling them how detailed of a report he needs (`--full-report` option) and for how many users (--limit option), and combinning their reports, so that the `CLI` can print it out on the screen.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require "bundler/gem_tasks"
5
+ require 'stefon/rake_task'
6
+
7
+
8
+ desc 'Run stefon over himself'
9
+ task :default do
10
+ Stefon::RakeTask.new
11
+ end
data/bin/stefon ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ if RUBY_VERSION >= '1.9.2' && RUBY_VERSION < '2.0.0'
5
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
6
+
7
+ # test for presence of sed dependency
8
+ sed_location = `which sed`
9
+ if sed_location == ''
10
+ puts 'Stefon requires the cmd line utility sed.\
11
+ Install it with: brew install sed'
12
+ exit(-1)
13
+ end
14
+
15
+ require 'stefon'
16
+ require 'benchmark'
17
+ require 'trollop'
18
+
19
+ cli, result, opts = Stefon::CLI.new
20
+
21
+ time = Benchmark.realtime do
22
+ opts = Trollop.options(&Stefon::Options.get)
23
+ result = cli.run(opts)
24
+ end
25
+
26
+ puts "Finished in #{time} seconds"
27
+ header = 'Stefon recommends that you ask for a code review from:'
28
+ puts header unless opts[:full_report]
29
+ puts result
30
+ exit(0)
31
+ else
32
+ puts 'Stefon supports only Ruby versions 1.9.X where X >= 2'
33
+ exit(-1)
34
+ end
data/lib/stefon.rb ADDED
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ require 'stefon/version'
4
+ require 'stefon/cli'
5
+ require 'stefon/config'
6
+
7
+ require 'stefon/surveyor/surveyor'
8
+ require 'stefon/surveyor/grit_util'
9
+ require 'stefon/surveyor/git_util'
10
+ require 'stefon/surveyor/added_files'
11
+ require 'stefon/surveyor/deleted_files'
12
+ require 'stefon/surveyor/added_lines'
13
+ require 'stefon/surveyor/deleted_lines'
14
+ require 'stefon/surveyor/editor'
data/lib/stefon/cli.rb ADDED
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ # This class is responsible for handling the command line interface
5
+ class CLI
6
+ # The entry point for the application logic
7
+ def run(opts)
8
+ options = opts.merge(Config::Weights.get)
9
+ editor = Stefon::Editor.new(options)
10
+ editor.summarize_results
11
+ end
12
+ end
13
+
14
+ # This module holds custom behavior for dealing with the gem trollop
15
+ module Options
16
+ def self.get
17
+ proc do
18
+ version "stefon #{Stefon::VERSION} (c) 2014 Ilya Kavalerov"
19
+ banner <<-EOS
20
+ Stefon is a utilty that recommends who to ask for a code review.
21
+ He lets you know whose code you are affecting the most.
22
+
23
+ Usage:
24
+ stefon [options]
25
+ where [options] are:
26
+ EOS
27
+
28
+ opt :limit, 'Limit the number of people that stephon suggests sending ' +
29
+ 'a code review to',
30
+ default: 4, short: '-l'
31
+ opt :full_report, "Boolean for whether or not to include information " +
32
+ "about how you affected someone's code",
33
+ default: false, short: '-f'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ # this class represents extra information that the editor needs
5
+ # to know before writing a script for Stefon
6
+ module Config
7
+ # This module is responsible for ruling out/in authors for the repository
8
+ # based on user specification, or repo members on github
9
+ module ExcludedAuthors
10
+ def valid?(author)
11
+ # exclude_filter(author) && include_filter(author)
12
+ true
13
+ end
14
+ end
15
+
16
+ # This module is in charge of providing weights to rank certain kind of
17
+ # edits over others, Eg. Deleting Stephanie's line of code is more
18
+ # important than adding a line of code to Stephanie's file
19
+ module Weights
20
+ attr_reader :default
21
+ attr_accessor :custom
22
+
23
+ def self.get(custom = nil)
24
+ # in the future this will be provided arguments by the config loader
25
+ # class after it loads them from yaml files
26
+ @default = {
27
+ deleted_line: 2,
28
+ deleted_file: 4,
29
+ added_line: 1,
30
+ added_file: 1
31
+ }
32
+ @default unless custom
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rake'
4
+ require 'rake/tasklib'
5
+
6
+ module Stefon
7
+ # Provides a single rake task.
8
+ class RakeTask < Rake::TaskLib
9
+ attr_accessor :name
10
+ attr_accessor :verbose
11
+ attr_accessor :fail_on_error
12
+ attr_accessor :patterns
13
+ attr_accessor :formatters
14
+
15
+ def initialize
16
+ desc 'Run Stefon'
17
+
18
+ RakeFileUtils.send(:verbose, verbose) do
19
+ run_task(verbose)
20
+ end
21
+ end
22
+
23
+ def run_task(verbose)
24
+ require 'stefon'
25
+ cli = CLI.new
26
+ puts 'Running Stefon...' if verbose
27
+ result = cli.run(limit: 4)
28
+ puts result
29
+ abort('Stefon failed!') if fail_on_error
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ module Surveyor
5
+ # This class gives points to the top commiter of the repo
6
+ # proportionally to each file that a user adds
7
+ class AddedFiles < Surveyor::Base
8
+ def call
9
+ score_added_files.weight_scores(@weight)
10
+ end
11
+
12
+ def call_verbose
13
+ # There is only 1 top commiter
14
+ author = GitUtil.top_commiter.first
15
+ Surveyor::SurveyorStore[[
16
+ [author, ["The top commiter in this repo is: #{author}"]]
17
+ ]]
18
+ end
19
+
20
+ def score_added_files
21
+ if (num_added_files = @@grit.repo.status.added.count) > 0
22
+ @scores[GitUtil.top_commiter] += num_added_files
23
+ end
24
+ @scores
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ module Surveyor
5
+ # This class gives points to the top author of a file in which
6
+ # a user deleted lines
7
+ class AddedLines < Surveyor::Base
8
+ def call
9
+ score_added_lines.weight_scores(@weight)
10
+ end
11
+
12
+ def call_verbose
13
+ array_version = score_added_lines.to_a.map do |pair|
14
+ desc = "Added #{pair.last} #{pair.last == 1 ? 'line' : 'lines' } " +
15
+ "to files written by: #{pair.first}"
16
+ [pair.first, [desc]]
17
+ end
18
+ Surveyor::SurveyorStore[array_version]
19
+ end
20
+
21
+ def score_added_lines
22
+ # give credit to the most frequent commiter in the file
23
+ added_lines_by_file.each_pair do |filename, numlines|
24
+ blame = @@grit.blame_for(filename)
25
+ top_author = @@grit.file_valid_top_author(blame, filename)
26
+ # multiplied by the number of lines that are added in the staged commit
27
+ @scores[top_author] += numlines
28
+ end
29
+ @scores
30
+ end
31
+
32
+ def added_lines_by_file
33
+ lines_per_file_store = Hash.new(0)
34
+ GitUtil.added_lines_by_file do |filename, line_in_file|
35
+ lines_per_file_store[filename] += 1
36
+ end
37
+ lines_per_file_store
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ module Surveyor
5
+ # This class gives points to the predominant author of a file
6
+ # that a user deletes
7
+ class DeletedFiles < Surveyor::Base
8
+ def call
9
+ score_deleted_files.weight_scores(@weight)
10
+ end
11
+
12
+ def call_verbose
13
+ array_version = score_deleted_files.to_a.map do |pair|
14
+ desc = "Deleted #{pair.last} #{pair.last == 1 ? 'file' : 'files' } " +
15
+ "written by: #{pair.first}"
16
+ [pair.first, [desc]]
17
+ end
18
+ Surveyor::SurveyorStore[array_version]
19
+ end
20
+
21
+ def score_deleted_files
22
+ @@grit.repo.status.deleted.keys.each do |filename|
23
+ blame = @@grit.blame_for(filename)
24
+ top_author = @@grit.file_valid_top_author(blame, filename)
25
+ @scores[top_author] += 1
26
+ end
27
+ @scores
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ module Surveyor
5
+ class DeletedLines < Surveyor::Base
6
+ def call
7
+ score_deleted_lines.weight_scores(@weight)
8
+ end
9
+
10
+ def call_verbose
11
+ array_version = score_deleted_lines.to_a.map do |pair|
12
+ desc = "Deleted #{pair.last} #{pair.last == 1 ? 'line' : 'lines' } " +
13
+ "written by: #{pair.first}"
14
+ [pair.first, [desc]]
15
+ end
16
+ Surveyor::SurveyorStore[array_version]
17
+ end
18
+
19
+ def score_deleted_lines
20
+ deleted_lines_by_file.each_pair do |filename, lines|
21
+ blame = @@grit.blame_for(filename)
22
+ lines.each do |deleted_line|
23
+ valid_author = @@grit.valid_line_author(blame, deleted_line)
24
+ @scores[valid_author] += 1 if valid_author
25
+ end
26
+ end
27
+ @scores
28
+ end
29
+
30
+ def deleted_lines_by_file
31
+ lines_by_file_store = Hash.new([])
32
+ GitUtil.deleted_lines_by_file do |filename, line_in_file|
33
+ lines_by_file_store[filename] += [line_in_file]
34
+ end
35
+ lines_by_file_store
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ # The editor is responsible for forming a team of surveyors and
5
+ # asking for and combining their results. The editor decides what story
6
+ # to run, i.e. to print recommendations or why recommendations are impossible
7
+ class Editor
8
+ attr_reader :options, :team
9
+ attr_accessor :errors
10
+
11
+
12
+ def initialize(options)
13
+ @options = options
14
+ # currently unused
15
+ @errors = []
16
+ # The editor has a team of surveyors, and tells them their importance
17
+ @team = [
18
+ Surveyor::AddedFiles.new(options[:added_file]),
19
+ Surveyor::AddedLines.new(options[:added_line]),
20
+ Surveyor::DeletedFiles.new(options[:deleted_file]),
21
+ Surveyor::DeletedLines.new(options[:deleted_line])
22
+ ]
23
+ end
24
+
25
+ def combine_short_reports
26
+ @team.reduce(Surveyor::SurveyorStore.new) do |a, e|
27
+ a.merge_scores(e.call)
28
+ end
29
+ end
30
+
31
+ def short_report
32
+ combine_short_reports.sort_by { |k, v| -v }
33
+ end
34
+
35
+ def combine_full_reports
36
+ @team.reduce(Surveyor::SurveyorStore.new([])) do |a, e|
37
+ a.merge_scores(e.call_verbose)
38
+ end
39
+ end
40
+
41
+ def full_report
42
+ # sort by the scores
43
+ combine_full_reports.sort_by { |k, v| -combine_short_reports[k] }
44
+ end
45
+
46
+ def summarize_results
47
+ if @options[:full_report]
48
+ full_report.first(@options[:limit]).map(&:last).flatten
49
+ else
50
+ short_report.first(@options[:limit]).map(&:first).flatten
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ module Surveyor
5
+ # A module of usefull utils for dealing with the git cli directly
6
+ module GitUtil
7
+ CURRENT_BRANCH ||= %x(git rev-parse --abbrev-ref HEAD).sub("\n", '')
8
+
9
+ def self.top_commiter
10
+ git_commiters = %x(git shortlog -s -n)
11
+ # make a hash of authors pointing to num commits
12
+ top_commiters = Hash[
13
+ git_commiters.split("\n").map do |numcommits_author|
14
+ numcommits_author.strip.split("\t").reverse
15
+ end
16
+ ]
17
+ top_commiters.sort_by { |a, numlines| numlines.to_i }.first
18
+ end
19
+
20
+ def self.diff_as_array(mode = nil)
21
+ # mode should be '+' for added lines, '-' for deleted lines, and
22
+ # none for all looks at a diff, optionally matches lines starting
23
+ # with mode char, cut off first char of each line
24
+ %x(git diff HEAD~#{GritUtil.new.num_sui_commits} -U0 |
25
+ #{mode ? "grep ^#{mode} | " : ""} sed 's/^.//'
26
+ ).split("\n").map(&:strip)
27
+ end
28
+
29
+ def self.added_lines_by_file(&block)
30
+ lines_by_file(diff_as_array('+'), '++', block)
31
+ end
32
+
33
+ def self.deleted_lines_by_file(&block)
34
+ lines_by_file(diff_as_array('-'), '--', block)
35
+ end
36
+
37
+ def self.lines_by_file(git_diff_as_array, filename_marker, block)
38
+ # delivers a line and its parent filename to a block
39
+ git_diff_as_array.each_with_index do |e, i|
40
+ line, lines_ahead = e, 1
41
+ # if the line is a filename, we want it to point to its lines
42
+ while (line[0..1] == filename_marker) && (i + lines_ahead < git_diff_as_array.length) do
43
+ next_line = git_diff_as_array[i + lines_ahead]
44
+ # next_lines should not be filenames
45
+ break if next_line[0..1] == filename_marker
46
+ block.call(line[5..-1], next_line)
47
+ lines_ahead += 1
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'grit'
4
+
5
+ module Stefon
6
+ module Surveyor
7
+ # A class that abstracts dealing with the grit gem while respecting/knowing some
8
+ # restrictions, namely excluding authors
9
+ class GritUtil
10
+ attr_reader :repo, :num_sui_commits, :last_xenocommit
11
+
12
+ include Config::ExcludedAuthors
13
+
14
+ # About the attribute names
15
+ # num_sui_commits is important when a user makes multiple commits when
16
+ # working on someone else's project, to ensure that diffs do not
17
+ # include recent changes made by the user, the number of commits by the
18
+ # user (sui ~ self / origin) should be taken into account when calling
19
+ # a diff, so as to compare changes against the last commit not made by
20
+ # the user (self), but by another person (xeno ~ not self / not origin)
21
+ def initialize
22
+ @repo = Grit::Repo.new('.')
23
+ commits = @repo.commits(GitUtil::CURRENT_BRANCH)
24
+ # If you are the only commiter _ever_, then num_sui_commits would be
25
+ # irrelevant perhaps an error should be raised? Since in this case
26
+ # only yourself would be recommended
27
+ @num_sui_commits = commits.find_index { |c| c.author.name != @repo.config['user.name'] } || 0
28
+ @last_xeno_commit = commits[@num_sui_commits]
29
+ end
30
+
31
+ def blame_for(filename)
32
+ @repo.blame(filename, @last_xeno_commit)
33
+ end
34
+
35
+ def valid_line_author(blame, line)
36
+ matched_line = blame.lines.detect { |l| l.line.strip == line }
37
+ author = matched_line.commit.author.name if matched_line
38
+ author if valid?(author)
39
+ end
40
+
41
+ def file_valid_top_author(blame, filename)
42
+ authored_lines = Hash.new(0)
43
+ blame.lines.each do |l|
44
+ author = l.commit.author.name
45
+ authored_lines[author] += 1 if valid?(author)
46
+ end
47
+ top_author_line_pair = authored_lines.max_by { |a, numlines| numlines }
48
+ top_author_line_pair.first if top_author_line_pair
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ module Surveyor
5
+ # A store for the scores that each surveyor calculates, it takes
6
+ # the form of a hash where author names are keys, and points are
7
+ # values. Points are counts of lines or commits that belong to a person
8
+ class SurveyorStore < ::Hash
9
+ def initialize(default_val = 0)
10
+ super(default_val)
11
+ end
12
+
13
+ def merge_scores(scores_hash)
14
+ dup = self.dup
15
+ scores_hash.each_pair do |name, score|
16
+ dup[name] += score
17
+ end
18
+ dup
19
+ end
20
+
21
+ def weight_scores(weight)
22
+ dup = self.dup
23
+ dup.each_pair do |name, score|
24
+ dup[name] *= weight
25
+ end
26
+ dup
27
+ end
28
+ end
29
+
30
+ # A scaffold for concrete surveyors, meant to be extended
31
+ # This class calculates whose code the gem user is affecting
32
+ # the most for a particular kind of behavior (eg. line / file deletion)
33
+ class Base
34
+ attr_reader :weight
35
+ attr_accessor :scores
36
+
37
+ def initialize(weight)
38
+ @@grit ||= GritUtil.new
39
+ @scores = SurveyorStore.new
40
+ @weight = weight
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Stefon
4
+ VERSION = '0.0.2'
5
+ end
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'CLI' do
6
+ describe 'integration spec' do
7
+ let(:cli) { Stefon::CLI.new }
8
+
9
+ context 'in general' do
10
+ it 'runs without errors' do
11
+ cli.run(limit: 4)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Stefon::Config do
6
+
7
+ describe 'reading configuration from files' do
8
+ it 'reads the default configuration'
9
+ it 'read the user provided configuration'
10
+ end
11
+
12
+ describe 'loading configuration from github' do
13
+ it 'can connect and form a list of users to filter by'
14
+ end
15
+
16
+ context 'without github options enabled' do
17
+ describe 'merging configurations' do
18
+ it 'ensures user configuration takes prescedence'
19
+ end
20
+ end
21
+
22
+ context 'with github enabled' do
23
+ describe 'merging configurations' do
24
+ it 'ensures github user configuration takes prescedence over user yml config'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
4
+ require 'stefon'
5
+ require 'pry'
6
+
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Editor' do
6
+ let(:options) {{
7
+ limit: 4,
8
+ deleted_line: 2,
9
+ deleted_file: 4,
10
+ added_line: 1,
11
+ added_file: 1
12
+ }}
13
+
14
+ describe 'summarize results (integration)' do
15
+ before(:each) do
16
+ [
17
+ Stefon::Surveyor::AddedFiles,
18
+ Stefon::Surveyor::AddedLines,
19
+ Stefon::Surveyor::DeletedFiles,
20
+ Stefon::Surveyor::DeletedLines
21
+ ].each do |klass|
22
+ klass.any_instance.stub(:call).and_return(
23
+ 'Cy Twombly' => 4,
24
+ 'Christian Boltanski' => 2
25
+ )
26
+ klass.any_instance.stub(:call_verbose).and_return(
27
+ 'Cy Twombly' => ['Deleted 4 lines by Cy Twombly'],
28
+ 'Christian Boltanski' => ['Deleted 2 lines by Christian Boltanski']
29
+ )
30
+ end
31
+ end
32
+
33
+ it 'does not include numbers in the short report' do
34
+ options[:full_report] = false
35
+ editor = Stefon::Editor.new(options)
36
+ results = editor.summarize_results
37
+ results.should include 'Cy Twombly'
38
+ results.should include 'Christian Boltanski'
39
+ results.each do |reported_result|
40
+ reported_result.should_not =~ /\d/
41
+ end
42
+ end
43
+
44
+ it 'reports numbers in the verbose report' do
45
+ options[:full_report] = true
46
+ editor = Stefon::Editor.new(options)
47
+ results = editor.summarize_results
48
+ results.any? { |line| line == 'Deleted 4 lines by Cy Twombly' }.should be_true
49
+ results.any? { |line| line == 'Deleted 2 lines by Christian Boltanski' }.should be_true
50
+ results.each do |reported_result|
51
+ unless reported_result =~ /The top commiter in this repo is/
52
+ reported_result.should =~ /\d/
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Stefon::Surveyor::GitUtil do
6
+ describe '#lines_by_file' do
7
+ let(:git_diff_as_array) {[
8
+ "-- a/bin/stefon",
9
+ "-- a/lib/stefon/surveyor/deleted_lines.rb",
10
+ "-- a/lib/stefon/surveyor/git_util.rb",
11
+ "block.call(line, next_line)",
12
+ "-- a/lib/stefon/surveyor/grit_util.rb",
13
+ "repo.blame(filename, @last_xenocommit)",
14
+ "-- a/spec/surveyor/git_util_spec.rb",
15
+ "describe Stefon::Surveyor::GitUtil do",
16
+ "describe '#lines_by_file' do",
17
+ "it 'correctly yields lines belonging to files'",
18
+ "end"
19
+ ]}
20
+ let(:right_structure) {{
21
+ "lib/stefon/surveyor/git_util.rb" => ["block.call(line, next_line)"],
22
+ "lib/stefon/surveyor/grit_util.rb" => ["repo.blame(filename, @last_xenocommit)"],
23
+ "spec/surveyor/git_util_spec.rb" => [
24
+ "describe Stefon::Surveyor::GitUtil do",
25
+ "describe '#lines_by_file' do",
26
+ "it 'correctly yields lines belonging to files'",
27
+ "end"
28
+ ]
29
+ }}
30
+ let(:save_structure) { Hash.new([]) }
31
+ let(:block) { ->(filename, line) { save_structure[filename] += [line] } }
32
+
33
+ it 'correctly yields lines belonging to files' do
34
+ Stefon::Surveyor::GitUtil.lines_by_file(git_diff_as_array, '--', block)
35
+ save_structure.should == right_structure
36
+ end
37
+ end
38
+
39
+ describe '#diff_as_array' do
40
+ it 'correctly formats git diff output for - mode' do
41
+ pending('Would test this, but relies on console methods like sed')
42
+ end
43
+ end
44
+
45
+ describe '#top_commiter' do
46
+ it 'returns the top commiter for a repo' do
47
+ pending('Would test this, but relies on console methods like sed')
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Stefon::Surveyor::SurveyorStore do
6
+ let(:first) { Stefon::Surveyor::SurveyorStore.new }
7
+ let(:second) { Stefon::Surveyor::SurveyorStore.new }
8
+
9
+ before(:each) do
10
+ first['Van Gogh'] += 1
11
+ second['Gerhard Richter'] += 1
12
+ end
13
+
14
+ it 'merges scores correctly' do
15
+ combo = first.merge_scores(second)
16
+ combo_same = second.merge_scores(first)
17
+ combo.should be == combo_same
18
+ combo.should == { 'Van Gogh' => 1, 'Gerhard Richter' => 1 }
19
+ end
20
+
21
+ it 'merges without mutating the receiver score' do
22
+ expect do
23
+ first.merge_scores(second)
24
+ end.to_not change { first }
25
+ end
26
+
27
+ it 'merges without mutating the message score' do
28
+ expect do
29
+ first.merge_scores(second)
30
+ end.to_not change { second }
31
+ end
32
+ end
data/stefon.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'stefon/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "stefon"
9
+ spec.version = Stefon::VERSION
10
+ spec.platform = Gem::Platform::RUBY
11
+ spec.authors = ["Ilya Kavalerov"]
12
+ spec.email = ["ilya@artsymail.com"]
13
+ spec.description = <<-EOF
14
+ A utility that recommends who to ask for a code review.
15
+ Stefon tells you whose code you are affecting the most.
16
+ EOF
17
+ spec.summary = %q{A utility that recommends who to ask for a code review}
18
+ spec.homepage = "https://github.com/ilyakava/stefon"
19
+ spec.license = "MIT"
20
+
21
+ spec.files = `git ls-files`.split($/)
22
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_runtime_dependency "grit", "2.5.0"
27
+ spec.add_runtime_dependency "trollop", "~> 2.0"
28
+ spec.add_development_dependency "bundler", "~> 1.3"
29
+ spec.add_development_dependency "rake"
30
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stefon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Kavalerov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: grit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: trollop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: ! " A utility that recommends who to ask for a code review.\n Stefon
70
+ tells you whose code you are affecting the most.\n"
71
+ email:
72
+ - ilya@artsymail.com
73
+ executables:
74
+ - stefon
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - .gitignore
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - bin/stefon
84
+ - lib/stefon.rb
85
+ - lib/stefon/cli.rb
86
+ - lib/stefon/config.rb
87
+ - lib/stefon/rake_task.rb
88
+ - lib/stefon/surveyor/added_files.rb
89
+ - lib/stefon/surveyor/added_lines.rb
90
+ - lib/stefon/surveyor/deleted_files.rb
91
+ - lib/stefon/surveyor/deleted_lines.rb
92
+ - lib/stefon/surveyor/editor.rb
93
+ - lib/stefon/surveyor/git_util.rb
94
+ - lib/stefon/surveyor/grit_util.rb
95
+ - lib/stefon/surveyor/surveyor.rb
96
+ - lib/stefon/version.rb
97
+ - spec/cli_spec.rb
98
+ - spec/config_spec.rb
99
+ - spec/spec_helper.rb
100
+ - spec/surveyor/editor_spec.rb
101
+ - spec/surveyor/git_util_spec.rb
102
+ - spec/surveyor/surveyor_spec.rb
103
+ - stefon.gemspec
104
+ homepage: https://github.com/ilyakava/stefon
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.1.5
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: A utility that recommends who to ask for a code review
128
+ test_files:
129
+ - spec/cli_spec.rb
130
+ - spec/config_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/surveyor/editor_spec.rb
133
+ - spec/surveyor/git_util_spec.rb
134
+ - spec/surveyor/surveyor_spec.rb