Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GitHub status formatter #144

Merged
merged 9 commits into from
Apr 17, 2016
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [#135](https://github.com/mmozuras/pronto/pull/135): add Bitbucket formatter.
* [#135](https://github.com/mmozuras/pronto/pull/135): add Bitbucket pull request formatter.
* [#134](https://github.com/mmozuras/pronto/pull/134): colorize text formatter.
* [#144](https://github.com/mmozuras/pronto/pull/144): add GitHub status formatter.

## 0.6.0

Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,28 @@ or, if you want comments to appear on pull request diff, instead of commit:
$ GITHUB_ACCESS_TOKEN=token PULL_REQUEST_ID=id pronto run -f github_pr -c origin/master
```

Use `GithubStatusFormatter` to submit [commit status](https://github.com/blog/1227-commit-status-api):

```sh
$ GITHUB_ACCESS_TOKEN=token pronto run -f github_status -c origin/master
Copy link
Member

@mmozuras mmozuras Apr 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it's worth showing that multiple formatters can be used here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll add one more example.
Although I believe using multiple formatters is not documented anywhere else.

```

It's possible to combine multiple formatters.
To get both pull request comments and commit status summary use:

```sh
$ GITHUB_ACCESS_TOKEN=token PULL_REQUEST_ID=id pronto run -f github_status github_pr -c origin/master
```

As an alternative, you can also set up a rake task:

```ruby
Pronto::GemNames.new.to_a.each { |gem_name| require "pronto/#{gem_name}" }

formatter = Pronto::Formatter::GithubFormatter.new # or GithubPullRequestFormatter
Pronto.run('origin/master', '.', formatter)
status_formatter = Pronto::Formatter::GithubStatusFormatter.new
formatters = [formatter, status_formatter]
Pronto.run('origin/master', '.', formatters)
```

### GitLab Integration
Expand Down
1 change: 1 addition & 0 deletions lib/pronto.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
require 'pronto/formatter/text_formatter'
require 'pronto/formatter/json_formatter'
require 'pronto/formatter/github_formatter'
require 'pronto/formatter/github_status_formatter'
require 'pronto/formatter/github_pull_request_formatter'
require 'pronto/formatter/gitlab_formatter'
require 'pronto/formatter/bitbucket_formatter'
Expand Down
1 change: 1 addition & 0 deletions lib/pronto/formatter/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def self.names

FORMATTERS = {
'github' => GithubFormatter,
'github_status' => GithubStatusFormatter,
'github_pr' => GithubPullRequestFormatter,
'gitlab' => GitlabFormatter,
'bitbucket' => BitbucketFormatter,
Expand Down
27 changes: 27 additions & 0 deletions lib/pronto/formatter/github_status_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require_relative 'github_status_formatter/status_builder'

module Pronto
module Formatter
class GithubStatusFormatter
def format(messages, repo, _)
client = Github.new(repo)
head = repo.head_commit_sha

messages_by_runner = messages.uniq.group_by(&:runner)

Runner.runners.each do |runner|
create_status(client, head, runner, messages_by_runner[runner] || [])
end
end

private

def create_status(client, sha, runner, messages)
builder = StatusBuilder.new(runner, messages)
status = Github::Status.new(sha, builder.state, builder.context, builder.description)

client.create_commit_status(status)
end
end
end
end
17 changes: 17 additions & 0 deletions lib/pronto/formatter/github_status_formatter/inflector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Pronto
module Formatter
class GithubStatusFormatter
class Inflector
def self.underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub(/::/, '/')
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!('-', '_')
word.downcase!
word
end
end
end
end
end
42 changes: 42 additions & 0 deletions lib/pronto/formatter/github_status_formatter/sentence.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Pronto
module Formatter
class GithubStatusFormatter
class Sentence
def initialize(words)
@words = words
end

def to_s
case words.size
when 0
''
when 1
words[0].to_s.dup
when 2
"#{words[0]}#{WORD_CONNECTORS[:two_words_connector]}#{words[1]}"
else
to_oxford_comma_sentence
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have you chosen oxford comma?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shamelessly ripped off and simplified the implementation from ActiveSupport.

end
end

private

attr_reader :words

WORD_CONNECTORS = {
words_connector: ', ',
two_words_connector: ' and ',
last_word_connector: ', and '
}.freeze

private_constant :WORD_CONNECTORS

def to_oxford_comma_sentence
"#{words[0...-1].join(WORD_CONNECTORS[:words_connector])}"\
"#{WORD_CONNECTORS[:last_word_connector]}"\
"#{words[-1]}"
end
end
end
end
end
76 changes: 76 additions & 0 deletions lib/pronto/formatter/github_status_formatter/status_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require_relative 'sentence'
require_relative 'inflector'

module Pronto
module Formatter
class GithubStatusFormatter
class StatusBuilder
def initialize(runner, messages)
@runner = runner
@messages = messages
end

def description
desc = map_description
desc.empty? ? NO_ISSUES_DESCRIPTION : "Found #{desc}."
end

def state
failure? ? :failure : :success
end

def context
Inflector.underscore(@runner.name)
end

private

def failure?
@messages.any? { |message| failure_message?(message) }
end

def failure_message?(message)
message_state(message) == :failure
end

def message_state(message)
DEFAULT_LEVEL_TO_STATE_MAPPING[message.level]
end

def map_description
words = count_issue_types.map do |issue_type, issue_count|
pluralize(issue_count, issue_type)
end

Sentence.new(words).to_s
end

def count_issue_types
counts = @messages.each_with_object(Hash.new(0)) do |message, r|
r[message.level] += 1
end
order_by_severity(counts)
end

def order_by_severity(counts)
Hash[counts.sort_by { |k, _v| Pronto::Message::LEVELS.index(k) }]
end

def pluralize(count, word)
"#{count} #{word}#{count > 1 ? 's' : ''}"
end

DEFAULT_LEVEL_TO_STATE_MAPPING = {
info: :success,
warning: :failure,
error: :failure,
fatal: :failure
}.freeze

NO_ISSUES_DESCRIPTION = 'Coast is clear!'.freeze

private_constant :DEFAULT_LEVEL_TO_STATE_MAPPING, :NO_ISSUES_DESCRIPTION
end
end
end
end
21 changes: 21 additions & 0 deletions lib/pronto/github.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ def create_pull_comment(comment)
comment.path, comment.position)
end

def create_commit_status(status)
sha = pull_sha || status.sha
@config.logger.log("Creating comment status on #{sha}")
client.create_status(slug,
sha,
status.state, context: status.context, description: status.description)
end

private

def slug
Expand Down Expand Up @@ -81,6 +89,19 @@ def pull_requests
@pull_requests ||= client.pull_requests(slug)
end

Status = Struct.new(:sha, :state, :context, :description) do
def ==(other)
sha == other.sha &&
state == other.state &&
context == other.context &&
description == other.description
end

def to_s
"[#{sha}] #{context} #{state} - #{description}"
end
end

Comment = Struct.new(:sha, :body, :path, :position) do
def ==(other)
position == other.position &&
Expand Down
7 changes: 6 additions & 1 deletion spec/pronto/formatter/formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ module Formatter
it { should be_an_instance_of GithubPullRequestFormatter }
end

context 'github_status' do
let(:name) { 'github_status' }
it { should be_an_instance_of GithubStatusFormatter }
end

context 'json' do
let(:name) { 'json' }
it { should be_an_instance_of JsonFormatter }
Expand Down Expand Up @@ -67,7 +72,7 @@ module Formatter

describe '.names' do
subject { Formatter.names }
it { should =~ %w(github github_pr gitlab bitbucket bitbucket_pr json checkstyle text null) }
it { should =~ %w(github github_pr github_status gitlab bitbucket bitbucket_pr json checkstyle text null) }
end
end
end
27 changes: 27 additions & 0 deletions spec/pronto/formatter/github_status_formatter/inflector_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Pronto
module Formatter
RSpec.describe GithubStatusFormatter::Inflector do
describe '#underscore' do
subject { described_class.underscore(class_name) }

context 'when class is just one word' do
let(:class_name) { 'Pronto::Runner' }

it { should == 'pronto/runner' }
end

context 'when class contains camel case' do
let(:class_name) { 'Pronto::FakeRunner' }

it { should == 'pronto/fake_runner' }
end

context 'when class contains acronym' do
let(:class_name) { 'Pronto::SUPERFakeRunner' }

it { should == 'pronto/super_fake_runner' }
end
end
end
end
end
43 changes: 43 additions & 0 deletions spec/pronto/formatter/github_status_formatter/sentence_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Pronto
module Formatter
RSpec.describe GithubStatusFormatter::Sentence do
let(:sentence) { described_class.new(words) }

describe '#to_s' do
subject { sentence.to_s }

context 'when no words' do
let(:words) { [] }

it 'returns empty string' do
subject.should == ''
end
end

context 'when 1 word' do
let(:words) { %w(eeny) }

it 'returns the word' do
subject.should == 'eeny'
end
end

context 'when 2 words' do
let(:words) { %w(eeny meeny) }

it 'uses and to join words' do
subject.should == 'eeny and meeny'
end
end

context 'when 3 words' do
let(:words) { %w(eeny meeny miny moe) }

it 'enumerates words using oxford comma' do
subject.should == 'eeny, meeny, miny, and moe'
end
end
end
end
end
end
Loading