danger 0.5.1 → 0.7.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 +4 -4
- data/README.md +62 -9
- data/lib/assets/PluginTemplate.rb.template +20 -0
- data/lib/danger/available_values.rb +3 -3
- data/lib/danger/ci_source/local_git_repo.rb +19 -22
- data/lib/danger/circle_api.rb +1 -1
- data/lib/danger/commands/init.rb +25 -24
- data/lib/danger/commands/init_helpers/interviewer.rb +1 -1
- data/lib/danger/commands/local.rb +7 -5
- data/lib/danger/commands/new_plugin.rb +43 -0
- data/lib/danger/commands/runner.rb +11 -5
- data/lib/danger/comment_generators/github.md.erb +21 -4
- data/lib/danger/core_ext/string.rb +13 -0
- data/lib/danger/dangerfile.rb +33 -6
- data/lib/danger/dangerfile_dsl.rb +100 -13
- data/lib/danger/environment_manager.rb +35 -0
- data/lib/danger/plugins/plugin.rb +26 -0
- data/lib/danger/plugins/protect_files.rb +34 -0
- data/lib/danger/request_sources/github.rb +102 -25
- data/lib/danger/scm_source/file_list.rb +12 -0
- data/lib/danger/scm_source/git_repo.rb +15 -11
- data/lib/danger/standard_error.rb +1 -1
- data/lib/danger/version.rb +2 -2
- data/lib/danger/violation.rb +10 -0
- data/lib/danger.rb +1 -0
- metadata +17 -11
data/lib/danger/dangerfile.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Danger
|
|
|
7
7
|
class Dangerfile
|
|
8
8
|
include Danger::Dangerfile::DSL
|
|
9
9
|
|
|
10
|
-
attr_accessor :env, :warnings, :errors, :messages, :verbose
|
|
10
|
+
attr_accessor :env, :warnings, :errors, :messages, :markdowns, :verbose
|
|
11
11
|
|
|
12
12
|
# @return [Pathname] the path where the Dangerfile was loaded from. It is nil
|
|
13
13
|
# if the Dangerfile was generated programmatically.
|
|
@@ -26,8 +26,8 @@ module Danger
|
|
|
26
26
|
rows = []
|
|
27
27
|
|
|
28
28
|
AvailableValues.all.each do |key|
|
|
29
|
-
next if key == :pr_body
|
|
30
29
|
value = self.send(key)
|
|
30
|
+
value = value.scan(/.{,80}/).to_a.each(&:strip!).join("\n") if key == :pr_body
|
|
31
31
|
|
|
32
32
|
# So that we either have one value per row
|
|
33
33
|
# or we have [] for an empty array
|
|
@@ -40,8 +40,8 @@ module Danger
|
|
|
40
40
|
rows << ["SCM", env.scm.class]
|
|
41
41
|
rows << ["Source", env.ci_source.class]
|
|
42
42
|
rows << ["Requests", env.request_source.class]
|
|
43
|
-
rows << ["Base Commit", env.
|
|
44
|
-
rows << ["Head Commit", env.
|
|
43
|
+
rows << ["Base Commit", env.meta_info_for_base]
|
|
44
|
+
rows << ["Head Commit", env.meta_info_for_head]
|
|
45
45
|
|
|
46
46
|
params = {}
|
|
47
47
|
params[:rows] = rows.each { |current| current[0] = current[0].yellow }
|
|
@@ -49,8 +49,6 @@ module Danger
|
|
|
49
49
|
|
|
50
50
|
puts ""
|
|
51
51
|
puts Terminal::Table.new(params)
|
|
52
|
-
puts "PR Body:"
|
|
53
|
-
puts self.pr_body.cyan
|
|
54
52
|
puts ""
|
|
55
53
|
end
|
|
56
54
|
|
|
@@ -92,5 +90,34 @@ module Danger
|
|
|
92
90
|
# rubocop:enable Lint/RescueException
|
|
93
91
|
end
|
|
94
92
|
end
|
|
93
|
+
|
|
94
|
+
def print_results
|
|
95
|
+
return if (self.errors + self.warnings + self.messages + self.markdowns).count == 0
|
|
96
|
+
|
|
97
|
+
puts ""
|
|
98
|
+
puts "danger results:"
|
|
99
|
+
[:errors, :warnings, :messages].each do |current|
|
|
100
|
+
params = {}
|
|
101
|
+
params[:rows] = self.send(current).collect { |a| [a.message] }
|
|
102
|
+
next unless params[:rows].count > 0
|
|
103
|
+
params[:title] = case current
|
|
104
|
+
when :errors
|
|
105
|
+
current.to_s.capitalize.red
|
|
106
|
+
when :warnings
|
|
107
|
+
current.to_s.capitalize.yellow
|
|
108
|
+
else
|
|
109
|
+
current.to_s.capitalize
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
puts ""
|
|
113
|
+
puts Terminal::Table.new(params)
|
|
114
|
+
puts ""
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
puts "Markdown: ".green if self.markdowns.count > 0
|
|
118
|
+
self.markdowns.each do |current_markdown|
|
|
119
|
+
puts current_markdown
|
|
120
|
+
end
|
|
121
|
+
end
|
|
95
122
|
end
|
|
96
123
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'danger/violation'
|
|
2
|
+
|
|
1
3
|
module Danger
|
|
2
4
|
class Dangerfile
|
|
3
5
|
module DSL
|
|
@@ -13,14 +15,63 @@ module Danger
|
|
|
13
15
|
self.warnings = []
|
|
14
16
|
self.errors = []
|
|
15
17
|
self.messages = []
|
|
18
|
+
self.markdowns = []
|
|
19
|
+
|
|
20
|
+
load_default_plugins
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def import(path)
|
|
24
|
+
raise "`import` requires a string" unless path.kind_of?(String)
|
|
25
|
+
path += ".rb" unless path.end_with?(".rb")
|
|
26
|
+
|
|
27
|
+
if path.start_with?("http")
|
|
28
|
+
import_url(path)
|
|
29
|
+
else
|
|
30
|
+
import_local(path)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Download a remote plugin and use it locally
|
|
35
|
+
#
|
|
36
|
+
# @param [String] url
|
|
37
|
+
# https URL to the Ruby file to use
|
|
38
|
+
def import_url(url)
|
|
39
|
+
raise "URL is not https, for security reasons `danger` only supports encrypted requests" unless url.start_with?("https://")
|
|
40
|
+
|
|
41
|
+
require 'tmpdir'
|
|
42
|
+
require 'faraday'
|
|
43
|
+
content = Faraday.get(url)
|
|
44
|
+
Dir.mktmpdir do |dir|
|
|
45
|
+
path = File.join(dir, "temporary_remote_action.rb")
|
|
46
|
+
File.write(path, content.body)
|
|
47
|
+
import_local(path)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Import one or more local plugins
|
|
52
|
+
#
|
|
53
|
+
# @param [String] path
|
|
54
|
+
# The path to the file to import
|
|
55
|
+
# Can also be a pattern (./**/*plugin.rb)
|
|
56
|
+
def import_local(path)
|
|
57
|
+
Dir[path].each do |file|
|
|
58
|
+
require File.expand_path(file) # without the expand_path it would fail if the path doesn't start with ./
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def should_ignore_violation(message)
|
|
63
|
+
env.request_source.ignored_violations.include? message
|
|
16
64
|
end
|
|
17
65
|
|
|
18
66
|
# Declares a CI blocking error
|
|
19
67
|
#
|
|
20
68
|
# @param [String] message
|
|
21
69
|
# The message to present to the user
|
|
22
|
-
|
|
23
|
-
|
|
70
|
+
# @param [Boolean] sticky
|
|
71
|
+
# Whether the message should be kept after it was fixed
|
|
72
|
+
def fail(message, sticky: true)
|
|
73
|
+
return if should_ignore_violation(message)
|
|
74
|
+
self.errors << Violation.new(message, sticky)
|
|
24
75
|
puts "Raising error '#{message}'"
|
|
25
76
|
end
|
|
26
77
|
|
|
@@ -28,8 +79,11 @@ module Danger
|
|
|
28
79
|
#
|
|
29
80
|
# @param [String] message
|
|
30
81
|
# The message to present to the user
|
|
31
|
-
|
|
32
|
-
|
|
82
|
+
# @param [Boolean] sticky
|
|
83
|
+
# Whether the message should be kept after it was fixed
|
|
84
|
+
def warn(message, sticky: true)
|
|
85
|
+
return if should_ignore_violation(message)
|
|
86
|
+
self.warnings << Violation.new(message, sticky)
|
|
33
87
|
puts "Printing warning '#{message}'"
|
|
34
88
|
end
|
|
35
89
|
|
|
@@ -37,28 +91,61 @@ module Danger
|
|
|
37
91
|
#
|
|
38
92
|
# @param [String] message
|
|
39
93
|
# The message to present to the user
|
|
40
|
-
|
|
41
|
-
|
|
94
|
+
# @param [Boolean] sticky
|
|
95
|
+
# Whether the message should be kept after it was fixed
|
|
96
|
+
def message(message, sticky: true)
|
|
97
|
+
self.messages << Violation.new(message, sticky)
|
|
42
98
|
puts "Printing message '#{message}'"
|
|
43
99
|
end
|
|
44
100
|
|
|
101
|
+
# Print markdown to below the table
|
|
102
|
+
#
|
|
103
|
+
# @param [String] message
|
|
104
|
+
# The markdown based message to be printed below the table
|
|
105
|
+
def markdown(message)
|
|
106
|
+
self.markdowns << message
|
|
107
|
+
puts "Printing markdown #{message}"
|
|
108
|
+
end
|
|
109
|
+
|
|
45
110
|
# When an undefined method is called, we check to see if it's something
|
|
46
111
|
# that either the `scm` or the `request_source` can handle.
|
|
47
112
|
# This opens us up to letting those object extend themselves naturally.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
52
|
-
|
|
113
|
+
# This will also look for plugins
|
|
114
|
+
def method_missing(method_sym, *arguments, &_block)
|
|
115
|
+
# SCM Source
|
|
53
116
|
if AvailableValues.scm.include?(method_sym)
|
|
54
|
-
# SCM Source
|
|
55
117
|
return env.scm.send(method_sym)
|
|
56
118
|
end
|
|
57
119
|
|
|
120
|
+
# Request Source
|
|
58
121
|
if AvailableValues.request_source.include?(method_sym)
|
|
59
|
-
# Request Source
|
|
60
122
|
return env.request_source.send(method_sym)
|
|
61
123
|
end
|
|
124
|
+
|
|
125
|
+
# Plugins
|
|
126
|
+
class_name = method_sym.to_s.danger_class
|
|
127
|
+
if Danger::Dangerfile::DSL.const_defined?(class_name)
|
|
128
|
+
plugin_ref = Danger::Dangerfile::DSL.const_get(class_name)
|
|
129
|
+
if plugin_ref < Plugin
|
|
130
|
+
plugin_ref.new(self).run(*arguments)
|
|
131
|
+
else
|
|
132
|
+
raise "'#{method_sym}' is not a valid danger plugin".red
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
raise "Unknown method '#{method_sym}', please check out the documentation for available plugins".red
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def load_default_plugins
|
|
142
|
+
Dir["./lib/danger/plugins/*.rb"].each do |file|
|
|
143
|
+
require File.expand_path(file)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Dir["./danger_plugins/*.rb"].each do |file|
|
|
147
|
+
require File.expand_path(file)
|
|
148
|
+
end
|
|
62
149
|
end
|
|
63
150
|
end
|
|
64
151
|
end
|
|
@@ -32,5 +32,40 @@ module Danger
|
|
|
32
32
|
|
|
33
33
|
self.scm = GitRepo.new # For now
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
def ensure_danger_branches_are_setup
|
|
37
|
+
# As this currently just works with GitHub, we can use a github specific feature here:
|
|
38
|
+
pull_id = ci_source.pull_request_id
|
|
39
|
+
test_branch = request_source.base_commit
|
|
40
|
+
|
|
41
|
+
# Next, we want to ensure that we have a version of the current branch that at a know location
|
|
42
|
+
scm.exec "branch #{danger_base_branch} #{test_branch}"
|
|
43
|
+
|
|
44
|
+
# OK, so we want to ensure that we have a known head branch, this will always represent
|
|
45
|
+
# the head ( e.g. the most recent commit that will be merged. )
|
|
46
|
+
scm.exec "fetch origin +refs/pull/#{pull_id}/merge:#{danger_head_branch}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def clean_up
|
|
50
|
+
[danger_base_branch, danger_base_branch].each do |branch|
|
|
51
|
+
scm.exec "branch -d #{branch}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def meta_info_for_base
|
|
56
|
+
scm.exec("--no-pager log #{danger_base_branch} -n1")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def meta_info_for_head
|
|
60
|
+
scm.exec("--no-pager log #{danger_head_branch} -n1")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def danger_head_branch
|
|
64
|
+
"danger_head"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def danger_base_branch
|
|
68
|
+
"danger_base"
|
|
69
|
+
end
|
|
35
70
|
end
|
|
36
71
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Danger
|
|
2
|
+
class Dangerfile
|
|
3
|
+
module DSL
|
|
4
|
+
class Plugin
|
|
5
|
+
def initialize(dsl)
|
|
6
|
+
@dsl = dsl
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Since we have a reference to the DSL containing all the information
|
|
10
|
+
# We need to redirect the self calls to the DSL
|
|
11
|
+
def method_missing(method_sym, *arguments, &_block)
|
|
12
|
+
return @dsl.send(method_sym, *arguments) if @dsl.respond_to?(method_sym)
|
|
13
|
+
return @dsl.method_missing(method_sym, *arguments)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
raise "run method must be implemented"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.description
|
|
21
|
+
"Add plugin description here"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Danger
|
|
2
|
+
class Dangerfile
|
|
3
|
+
module DSL
|
|
4
|
+
class ProtectFiles < Plugin
|
|
5
|
+
def run(path: nil, message: nil, fail_build: true)
|
|
6
|
+
raise "You have to provide a message" if message.to_s.length == 0
|
|
7
|
+
raise "You have to provide a path" if path.to_s.length == 0
|
|
8
|
+
|
|
9
|
+
broken_rule = false
|
|
10
|
+
|
|
11
|
+
Dir.glob(path) do |current|
|
|
12
|
+
broken_rule = true if self.env.scm.modified_files.include?(current)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
return unless broken_rule
|
|
16
|
+
|
|
17
|
+
if fail_build
|
|
18
|
+
@dsl.errors << message
|
|
19
|
+
else
|
|
20
|
+
@dsl.warnings << message
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.description
|
|
25
|
+
[
|
|
26
|
+
"Protect a file from being changed. This can",
|
|
27
|
+
"be used in combination with some kind of",
|
|
28
|
+
"permission check if a user is inside the org"
|
|
29
|
+
].join(" ")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -4,7 +4,7 @@ require 'redcarpet'
|
|
|
4
4
|
|
|
5
5
|
module Danger
|
|
6
6
|
class GitHub
|
|
7
|
-
attr_accessor :ci_source, :pr_json, :issue_json, :environment, :base_commit, :head_commit, :support_tokenless_auth
|
|
7
|
+
attr_accessor :ci_source, :pr_json, :issue_json, :environment, :base_commit, :head_commit, :support_tokenless_auth, :ignored_violations, :github_host
|
|
8
8
|
|
|
9
9
|
def initialize(ci_source, environment)
|
|
10
10
|
self.ci_source = ci_source
|
|
@@ -12,24 +12,35 @@ module Danger
|
|
|
12
12
|
self.support_tokenless_auth = false
|
|
13
13
|
|
|
14
14
|
Octokit.auto_paginate = true
|
|
15
|
+
@token = @environment["DANGER_GITHUB_API_TOKEN"]
|
|
16
|
+
self.github_host = @environment["DANGER_GITHUB_HOST"] || "github.com"
|
|
17
|
+
if @environment["DANGER_GITHUB_API_HOST"]
|
|
18
|
+
Octokit.api_endpoint = @environment["DANGER_GITHUB_API_HOST"]
|
|
19
|
+
end
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
def client
|
|
18
|
-
|
|
19
|
-
raise "No API given, please provide one using `DANGER_GITHUB_API_TOKEN`" if !token && !support_tokenless_auth
|
|
23
|
+
raise "No API given, please provide one using `DANGER_GITHUB_API_TOKEN`" if !@token && !support_tokenless_auth
|
|
20
24
|
|
|
21
25
|
@client ||= Octokit::Client.new(
|
|
22
|
-
access_token: token
|
|
26
|
+
access_token: @token
|
|
23
27
|
)
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def markdown_parser
|
|
27
|
-
@markdown_parser ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
|
|
31
|
+
@markdown_parser ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: true)
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def fetch_details
|
|
31
35
|
self.pr_json = client.pull_request(ci_source.repo_slug, ci_source.pull_request_id)
|
|
32
36
|
fetch_issue_details(self.pr_json)
|
|
37
|
+
self.ignored_violations = ignored_violations_from_pr(self.pr_json)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ignored_violations_from_pr(pr_json)
|
|
41
|
+
pr_body = pr_json[:body]
|
|
42
|
+
return [] if pr_body.nil?
|
|
43
|
+
pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
|
|
33
44
|
end
|
|
34
45
|
|
|
35
46
|
def fetch_issue_details(pr_json)
|
|
@@ -45,16 +56,20 @@ module Danger
|
|
|
45
56
|
self.pr_json[:head][:sha]
|
|
46
57
|
end
|
|
47
58
|
|
|
59
|
+
def branch_for_merge
|
|
60
|
+
self.pr_json[:base][:ref]
|
|
61
|
+
end
|
|
62
|
+
|
|
48
63
|
def pr_title
|
|
49
|
-
self.pr_json[:title]
|
|
64
|
+
self.pr_json[:title].to_s
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
def pr_body
|
|
53
|
-
self.pr_json[:body]
|
|
68
|
+
self.pr_json[:body].to_s
|
|
54
69
|
end
|
|
55
70
|
|
|
56
71
|
def pr_author
|
|
57
|
-
self.pr_json[:user][:login]
|
|
72
|
+
self.pr_json[:user][:login].to_s
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
def pr_labels
|
|
@@ -62,16 +77,28 @@ module Danger
|
|
|
62
77
|
end
|
|
63
78
|
|
|
64
79
|
# Sending data to GitHub
|
|
65
|
-
def update_pull_request!(warnings:
|
|
80
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [])
|
|
66
81
|
comment_result = {}
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
issues = client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
|
|
84
|
+
editable_issues = issues.reject { |issue| issue[:body].include?("generated_by_danger") == false }
|
|
85
|
+
|
|
86
|
+
if editable_issues.empty?
|
|
87
|
+
previous_violations = {}
|
|
88
|
+
else
|
|
89
|
+
comment = editable_issues.first[:body]
|
|
90
|
+
previous_violations = parse_comment(comment)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if previous_violations.empty? && (warnings + errors + messages + markdowns).empty?
|
|
69
94
|
# Just remove the comment, if there's nothing to say.
|
|
70
95
|
delete_old_comments!
|
|
71
96
|
else
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
body = generate_comment(warnings: warnings,
|
|
98
|
+
errors: errors,
|
|
99
|
+
messages: messages,
|
|
100
|
+
markdowns: markdowns,
|
|
101
|
+
previous_violations: previous_violations)
|
|
75
102
|
|
|
76
103
|
if editable_issues.empty?
|
|
77
104
|
comment_result = client.add_comment(ci_source.repo_slug, ci_source.pull_request_id, body)
|
|
@@ -93,7 +120,7 @@ module Danger
|
|
|
93
120
|
message = generate_github_description(warnings: warnings, errors: errors)
|
|
94
121
|
client.create_status(ci_source.repo_slug, latest_pr_commit_ref, status, {
|
|
95
122
|
description: message,
|
|
96
|
-
context: "
|
|
123
|
+
context: "danger/danger",
|
|
97
124
|
target_url: details_url
|
|
98
125
|
})
|
|
99
126
|
rescue
|
|
@@ -118,11 +145,15 @@ module Danger
|
|
|
118
145
|
end
|
|
119
146
|
end
|
|
120
147
|
|
|
148
|
+
def random_compliment
|
|
149
|
+
compliment = ["Well done.", "Congrats.", "Woo!",
|
|
150
|
+
"Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."]
|
|
151
|
+
compliment.sample
|
|
152
|
+
end
|
|
153
|
+
|
|
121
154
|
def generate_github_description(warnings: nil, errors: nil)
|
|
122
155
|
if errors.empty? && warnings.empty?
|
|
123
|
-
|
|
124
|
-
"Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."]
|
|
125
|
-
return "All green. #{compliment.sample}"
|
|
156
|
+
return "All green. #{random_compliment}"
|
|
126
157
|
else
|
|
127
158
|
message = "⚠ "
|
|
128
159
|
message += "#{errors.count} Error#{errors.count == 1 ? '' : 's'}. " unless errors.empty?
|
|
@@ -132,7 +163,7 @@ module Danger
|
|
|
132
163
|
end
|
|
133
164
|
end
|
|
134
165
|
|
|
135
|
-
def generate_comment(warnings: [], errors: [], messages: [])
|
|
166
|
+
def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {})
|
|
136
167
|
require 'erb'
|
|
137
168
|
|
|
138
169
|
md_template = File.join(Danger.gem_path, "lib/danger/comment_generators/github.md.erb")
|
|
@@ -140,18 +171,64 @@ module Danger
|
|
|
140
171
|
# erb: http://www.rrn.dk/rubys-erb-templating-system
|
|
141
172
|
# for the extra args: http://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
|
|
142
173
|
@tables = [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
174
|
+
table("Error", "no_entry_sign", errors, previous_violations),
|
|
175
|
+
table("Warning", "warning", warnings, previous_violations),
|
|
176
|
+
table("Message", "book", messages, previous_violations)
|
|
146
177
|
]
|
|
178
|
+
@markdowns = markdowns
|
|
179
|
+
|
|
147
180
|
return ERB.new(File.read(md_template), 0, "-").result(binding)
|
|
148
181
|
end
|
|
149
182
|
|
|
150
|
-
def
|
|
151
|
-
|
|
183
|
+
def table(name, emoji, violations, all_previous_violations)
|
|
184
|
+
content = violations.map { |v| process_markdown(v) }
|
|
185
|
+
kind = table_kind_from_title(name)
|
|
186
|
+
previous_violations = all_previous_violations[kind] || []
|
|
187
|
+
messages = content.map(&:message)
|
|
188
|
+
resolved_violations = previous_violations.reject { |s| messages.include? s }
|
|
189
|
+
count = content.count
|
|
190
|
+
{ name: name, emoji: emoji, content: content, resolved: resolved_violations, count: count }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def parse_comment(comment)
|
|
194
|
+
tables = parse_tables_from_comment(comment)
|
|
195
|
+
violations = {}
|
|
196
|
+
tables.each do |table|
|
|
197
|
+
next unless table =~ %r{<th width="100%"(.*?)</th>}im
|
|
198
|
+
title = Regexp.last_match(1)
|
|
199
|
+
kind = table_kind_from_title(title)
|
|
200
|
+
next unless kind
|
|
201
|
+
|
|
202
|
+
violations[kind] = violations_from_table(table)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
violations.reject { |_, v| v.empty? }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def violations_from_table(table)
|
|
209
|
+
regex = %r{<td data-sticky="true">(?:<del>)?(.*?)(?:</del>)?\s*</td>}im
|
|
210
|
+
table.scan(regex).flatten.map(&:strip)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def table_kind_from_title(title)
|
|
214
|
+
if title =~ /error/i
|
|
215
|
+
:error
|
|
216
|
+
elsif title =~ /warning/i
|
|
217
|
+
:warning
|
|
218
|
+
elsif title =~ /message/i
|
|
219
|
+
:message
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def parse_tables_from_comment(comment)
|
|
224
|
+
comment.split('</table>')
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def process_markdown(violation)
|
|
228
|
+
html = markdown_parser.render(violation.message)
|
|
152
229
|
match = html.match(%r{^<p>(.*)</p>$})
|
|
153
|
-
|
|
154
|
-
|
|
230
|
+
message = match.nil? ? html : match.captures.first
|
|
231
|
+
Violation.new(message, violation.sticky)
|
|
155
232
|
end
|
|
156
233
|
end
|
|
157
234
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Danger
|
|
2
|
+
class FileList < Array
|
|
3
|
+
# Information about pattern: http://ruby-doc.org/core-2.2.0/File.html#method-c-fnmatch
|
|
4
|
+
# e.g. "**/something.*" for any file called something with any extension
|
|
5
|
+
def include?(pattern)
|
|
6
|
+
self.each do |current|
|
|
7
|
+
return true if File.fnmatch(pattern, current)
|
|
8
|
+
end
|
|
9
|
+
return false
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
# For more info see: https://github.com/schacon/ruby-git
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'git'
|
|
4
4
|
|
|
5
5
|
module Danger
|
|
6
6
|
class GitRepo
|
|
7
7
|
attr_accessor :diff
|
|
8
8
|
|
|
9
9
|
def diff_for_folder(folder, from: "master", to: 'HEAD')
|
|
10
|
-
repo =
|
|
10
|
+
repo = Git.open folder
|
|
11
11
|
self.diff = repo.diff(from, to)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
|
|
14
|
+
def exec(string)
|
|
15
|
+
`git #{string}`.strip
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def
|
|
19
|
-
@diff.select
|
|
18
|
+
def added_files
|
|
19
|
+
Danger::FileList.new(@diff.select { |diff| diff.type == "new" }.map(&:path))
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def
|
|
23
|
-
@diff.
|
|
22
|
+
def deleted_files
|
|
23
|
+
Danger::FileList.new(@diff.select { |diff| diff.type == "deleted" }.map(&:path))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def modified_files
|
|
27
|
+
Danger::FileList.new(@diff.stats[:files].keys)
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def lines_of_code
|
|
27
|
-
@diff.
|
|
31
|
+
@diff.lines
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def deletions
|
|
31
|
-
@diff.
|
|
35
|
+
@diff.deletions
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def insertions
|
|
35
|
-
@diff.
|
|
39
|
+
@diff.insertions
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
end
|
data/lib/danger/version.rb
CHANGED