danger 0.6.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 583874b396b59f83aa7e2150e2a11072d7a05b1b
4
- data.tar.gz: a5391cce92d93f75753a903cbe1d395b337af929
3
+ metadata.gz: f405c58b164cfb051f33ce62eff96d6954ccedf8
4
+ data.tar.gz: e487a8349af60005ce0f63ec429cd8e73a561055
5
5
  SHA512:
6
- metadata.gz: 4b6fc6777fcd152333dbf0cdbe4c48c23f243aa4711c51b0fc149cf2d4de20ea1dd1c8bf7f4f3864a3dd55b0e4818f4e06e9963bb079e1d3642b26804f82c727
7
- data.tar.gz: 946a75817a7d7fd2c1966c4177cd64cf229e017c228b63dbdbab324f4f2747e1610510cfabde9e498ecfb879eca5029a85433533654c05df94cdc5d1fa266ae8
6
+ metadata.gz: 3ce00623811798d3bf0cb0e77bc788bc0c99fac1045d00a33cf3c68b297983e669de41544bae3f8310257e3a34a3e8704aedb2d9f9fc2064e4f0a3e443cdcbc5
7
+ data.tar.gz: e0cdd1d536430c45226a3638ff0a2cd659db62a19e643c931230961e00967e7705321ec561c2afd276c0e73f6570d2335f7126b82017be95f5dcd0de18d9c906
data/README.md CHANGED
@@ -67,14 +67,14 @@ The `Dangerfile` is a ruby file, so really, you can do anything. However, at thi
67
67
  declared_trivial = pr_title.include? "#trivial"
68
68
 
69
69
  # Just to let people know
70
- warn("PR is classed as Work in Progress") if pr_title.include? "[WIP]"
70
+ warn("PR is classed as Work in Progress", sticky: false) if pr_title.include? "[WIP]"
71
71
  ```
72
72
 
73
73
  #### Being cautious around specific files
74
74
 
75
75
  ``` ruby
76
76
  # Devs shouldn't ship changes to this file
77
- fail("Developer Specific file shouldn't be changed") if modified_files.include?("Artsy/View_Controllers/App_Navigation/ARTopMenuViewController+DeveloperExtras.m")
77
+ fail("Developer Specific file shouldn't be changed", sticky: false) if modified_files.include?("Artsy/View_Controllers/App_Navigation/ARTopMenuViewController+DeveloperExtras.m")
78
78
 
79
79
  # Did you make analytics changes? Well you should also include a change to our analytics spec
80
80
  made_analytics_changes = modified_files.include?("/Artsy/App/ARAppDelegate+Analytics.m")
@@ -102,6 +102,40 @@ snapshots_url = build_log.match(%r{https://eigen-ci.s3.amazonaws.com/\d+/index.h
102
102
  fail("There were [snapshot errors](#{snapshots_url})") if snapshots_url
103
103
  ```
104
104
 
105
+ #### Available commands
106
+
107
+ Command | Description
108
+ ------------- | ----
109
+ `fail` | Causes the PR to fail and print out the error on the PR
110
+ `warn` | Prints out a warning to the PR, but still enables the merge button
111
+ `message` | Show neutral messages on the PR
112
+ `markdown` | Print raw markdown below the summary tables on the PR
113
+
114
+ ## Plugins
115
+
116
+ Danger was built with a platform in mind: It can be used with any kind of software project and allows you to write your own action to have structured source code.
117
+
118
+ In your `Dangerfile` you can import local or remote actions using
119
+
120
+ ```ruby
121
+ import "./danger_plugins/work_in_progress_warning"
122
+ # or
123
+ import "https://raw.githubusercontent.com/danger/danger/master/danger_plugins/work_in_progress_warning.rb"
124
+
125
+ # Call those actions using
126
+ work_in_progress_warning
127
+
128
+ custom_plugin(variable: "value")
129
+ ```
130
+
131
+ To create a new plugin run
132
+
133
+ ```
134
+ danger new_plugin
135
+ ```
136
+
137
+ This will generate a new Ruby file which you can modify to fit your needs.
138
+
105
139
  ## Support
106
140
 
107
141
  Danger currently is supported on Travis CI, Circle CI, BuildKite and Jenkins. These work via environment variables, so it's easy to extend to include your own.
@@ -132,6 +166,15 @@ You can tell Danger to ignore a specific warning or error by commenting on the P
132
166
  > Danger: Ignore "Developer Specific file shouldn't be changed"
133
167
  ```
134
168
 
169
+ ## Sticky
170
+
171
+ Danger can keep its history if a warning/error/message is marked as *sticky*. When the violation is resolved,
172
+ Danger will update the comment to cross it out. If you don't want this behavior, just use `sticky: false`.
173
+
174
+ ```ruby
175
+ fail("PR needs labels", sticky: false) if pr_labels.empty?
176
+ ```
177
+
135
178
  ## Useful bits of knowledge
136
179
 
137
180
  * You can set the base branch in the command line arguments see: `bundle exec danger --help`, if you commonly merge into non-master branches.
@@ -141,6 +184,8 @@ Here are some real-world Dangerfiles: [artsy/eigen](https://github.com/artsy/eig
141
184
 
142
185
  ## License, Contributor's Guidelines and Code of Conduct
143
186
 
187
+ [Join our Slack Group](https://danger-slack.herokuapp.com/)
188
+
144
189
  > This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs.
145
190
 
146
191
  > This project subscribes to the [Moya Contributors Guidelines](https://github.com/Moya/contributors) which TLDR: means we give out push access easily and often.
@@ -0,0 +1,20 @@
1
+ module Danger
2
+ class Dangerfile
3
+ module DSL
4
+ class [[CLASS_NAME]] < Plugin
5
+ def run(parameter1: nil, parameter2: nil)
6
+ if (pr_body + pr_title).include?("WIP")
7
+ warn "Pull Request is Work in Progress"
8
+ end
9
+ end
10
+
11
+ def self.description
12
+ [
13
+ "Describe what this plugin does",
14
+ "and how the user can use it"
15
+ ].join(" ")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/danger.rb CHANGED
@@ -3,6 +3,7 @@ require "danger/dangerfile"
3
3
  require "danger/environment_manager"
4
4
  require "danger/commands/runner"
5
5
  require "danger/available_values"
6
+ require "danger/core_ext/string"
6
7
 
7
8
  require "claide"
8
9
  require "colored"
@@ -36,13 +36,15 @@ module Danger
36
36
 
37
37
  # get the most recent PR merge
38
38
  pr_merge = run_git "log --since='2 weeks ago' --merges --oneline | grep \"Merge pull request\" | head -n 1".strip
39
- if pr_merge
40
- self.pull_request_id = pr_merge.match("#([0-9]+)")[1]
41
- sha = pr_merge.split(" ")[0]
42
- parents = run_git("rev-list --parents -n 1 #{sha}").strip.split(" ")
43
- self.base_commit = parents[0]
44
- self.head_commit = parents[1]
39
+ if pr_merge.to_s.empty?
40
+ raise "No recent pull requests found for this repo, danger requires at least one PR for the local mode"
45
41
  end
42
+
43
+ self.pull_request_id = pr_merge.match("#([0-9]+)")[1]
44
+ sha = pr_merge.split(" ")[0]
45
+ parents = run_git("rev-list --parents -n 1 #{sha}").strip.split(" ")
46
+ self.base_commit = parents[0]
47
+ self.head_commit = parents[1]
46
48
  end
47
49
  end
48
50
  end
@@ -56,7 +56,7 @@ module Danger
56
56
  show_prompt
57
57
  answer = STDIN.gets.chomp
58
58
 
59
- break if answer.length > 0
59
+ break if answer.empty?
60
60
 
61
61
  print "\nYou need to provide an answer."
62
62
  end
@@ -48,7 +48,8 @@ module Danger
48
48
  dm.env.scm = GitRepo.new
49
49
 
50
50
  dm.env.scm.diff_for_folder(".", from: dm.env.ci_source.base_commit, to: dm.env.ci_source.head_commit)
51
- dm.parse Pathname.new(@dangerfile_path)
51
+ dm.parse(Pathname.new(@dangerfile_path))
52
+ dm.print_results
52
53
  end
53
54
  end
54
55
  end
@@ -0,0 +1,43 @@
1
+ module Danger
2
+ class NewPlugin < Runner
3
+ self.summary = 'Generate a new danger plugin.'
4
+ self.command = 'new_plugin'
5
+
6
+ def initialize(argv)
7
+ super
8
+ end
9
+
10
+ def validate!
11
+ super
12
+ end
13
+
14
+ def run
15
+ require 'fileutils'
16
+
17
+ puts "Must be lower case, and use a '_' between words. Do not use '.'".green
18
+ puts "examples: 'number_of_emojis', 'ensure_pr_title_contains_keyword'".green
19
+ puts "Name of your new plugin: "
20
+ name = STDIN.gets.strip
21
+
22
+ dir = Danger.gem_path
23
+ content = File.read(File.join(dir, "lib", "assets", "PluginTemplate.rb.template"))
24
+ content.gsub!("[[CLASS_NAME]]", name.danger_class)
25
+
26
+ plugins_path = "danger_plugins"
27
+ FileUtils.mkdir_p("plugins_path") unless File.directory?(plugins_path)
28
+
29
+ output_path = File.join(plugins_path, "#{name}.rb")
30
+ raise "File '#{output_path}' already exists!" if File.exist?(output_path)
31
+ File.write(output_path, content)
32
+
33
+ puts ""
34
+ puts "Successfully created new plugin at path '#{output_path}'".green
35
+ puts "Add this to your `Dangerfile` to use it:"
36
+ puts ""
37
+ puts "import \"#{output_path.gsub('.rb', '')}\"".blue
38
+ puts "#{name}(parameter1: 123, parameter2: \"Club Mate\")".blue
39
+ puts ""
40
+ puts "Enjoy ✨"
41
+ end
42
+ end
43
+ end
@@ -2,6 +2,7 @@ module Danger
2
2
  class Runner < CLAide::Command
3
3
  require 'danger/commands/init'
4
4
  require 'danger/commands/local'
5
+ require 'danger/commands/new_plugin'
5
6
 
6
7
  self.summary = 'Run the Dangerfile.'
7
8
  self.command = 'danger'
@@ -47,11 +48,13 @@ module Danger
47
48
  post_results(dm)
48
49
 
49
50
  dm.env.clean_up
51
+
52
+ dm.print_results
50
53
  end
51
54
 
52
55
  def post_results(dm)
53
56
  gh = dm.env.request_source
54
- gh.update_pull_request!(warnings: dm.warnings, errors: dm.errors, messages: dm.messages)
57
+ gh.update_pull_request!(warnings: dm.warnings, errors: dm.errors, messages: dm.messages, markdowns: dm.markdowns)
55
58
  end
56
59
  end
57
60
  end
@@ -1,24 +1,41 @@
1
1
  <% @tables.each do |table| %>
2
- <% if table[:content].any? %>
2
+ <% if table[:content].any? || table[:resolved].any? %>
3
3
  <table>
4
4
  <thead>
5
5
  <tr>
6
6
  <th width="50"></th>
7
- <th width="100%"><%= table[:content].count %> <%= table[:name] %><%= "s" unless table[:content].count == 1 %></th>
7
+ <th width="100%" data-kind="<%= table[:name] %>">
8
+ <% if table[:count] > 0 %>
9
+ <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %>
10
+ <% else %>
11
+ :white_check_mark: <%= random_compliment %>
12
+ <% end %>
13
+ </th>
8
14
  </tr>
9
15
  </thead>
10
16
  <tbody>
11
- <% table[:content].each do |message| -%>
17
+ <% table[:content].each do |violation| -%>
12
18
  <tr>
13
19
  <td>:<%= table[:emoji] %>:</td>
14
- <td><%= message %></td>
20
+ <td data-sticky="<%= violation.sticky %>"><%= violation.message %></td>
15
21
  </tr>
16
22
  <% end %>
23
+ <% table[:resolved].each do |message| -%>
24
+ <tr>
25
+ <td>:white_check_mark:</td>
26
+ <td data-sticky="true"><del><%= message %></del></td>
27
+ </tr>
28
+ <% end %>
17
29
  </tbody>
18
30
  </table>
19
31
  <% end %>
20
32
  <% end %>
21
33
 
34
+ <% @markdowns.each do |current| %>
35
+ <%= current %>
36
+ <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
37
+ <% end %>
38
+
22
39
  <p align="right" data-meta="generated_by_danger" data-base-commit="<%= @base_commit %>" data-head-commit="<%= @head_commit %>" >
23
40
  Generated by :no_entry_sign: <a href="https://github.com/danger/danger/">danger</a>
24
41
  </p>
@@ -0,0 +1,13 @@
1
+ class String
2
+ def danger_class
3
+ split('_').collect!(&:capitalize).join
4
+ end
5
+
6
+ def danger_underscore
7
+ self.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+ end
@@ -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.
@@ -90,5 +90,34 @@ module Danger
90
90
  # rubocop:enable Lint/RescueException
91
91
  end
92
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
93
122
  end
94
123
  end
@@ -1,3 +1,5 @@
1
+ require 'danger/violation'
2
+
1
3
  module Danger
2
4
  class Dangerfile
3
5
  module DSL
@@ -13,12 +15,47 @@ module Danger
13
15
  self.warnings = []
14
16
  self.errors = []
15
17
  self.messages = []
16
- load_plugins
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
17
32
  end
18
33
 
19
- def load_plugins
20
- Dir['./lib/danger/plugins/*.rb'].each do |file|
21
- require file
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 ./
22
59
  end
23
60
  end
24
61
 
@@ -30,9 +67,11 @@ module Danger
30
67
  #
31
68
  # @param [String] message
32
69
  # The message to present to the user
33
- def fail(message)
70
+ # @param [Boolean] sticky
71
+ # Whether the message should be kept after it was fixed
72
+ def fail(message, sticky: true)
34
73
  return if should_ignore_violation(message)
35
- self.errors << message
74
+ self.errors << Violation.new(message, sticky)
36
75
  puts "Raising error '#{message}'"
37
76
  end
38
77
 
@@ -40,9 +79,11 @@ module Danger
40
79
  #
41
80
  # @param [String] message
42
81
  # The message to present to the user
43
- def warn(message)
82
+ # @param [Boolean] sticky
83
+ # Whether the message should be kept after it was fixed
84
+ def warn(message, sticky: true)
44
85
  return if should_ignore_violation(message)
45
- self.warnings << message
86
+ self.warnings << Violation.new(message, sticky)
46
87
  puts "Printing warning '#{message}'"
47
88
  end
48
89
 
@@ -50,26 +91,61 @@ module Danger
50
91
  #
51
92
  # @param [String] message
52
93
  # The message to present to the user
53
- def message(message)
54
- self.messages << message
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)
55
98
  puts "Printing message '#{message}'"
56
99
  end
57
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
+
58
110
  # When an undefined method is called, we check to see if it's something
59
111
  # that either the `scm` or the `request_source` can handle.
60
112
  # This opens us up to letting those object extend themselves naturally.
61
- def method_missing(method_sym, *_arguments, &_block)
113
+ # This will also look for plugins
114
+ def method_missing(method_sym, *arguments, &_block)
115
+ # SCM Source
62
116
  if AvailableValues.scm.include?(method_sym)
63
- # SCM Source
64
117
  return env.scm.send(method_sym)
65
118
  end
66
119
 
120
+ # Request Source
67
121
  if AvailableValues.request_source.include?(method_sym)
68
- # Request Source
69
122
  return env.request_source.send(method_sym)
70
123
  end
71
124
 
72
- raise "Unknown method '#{method_sym}', please check out the documentation for available variables".red
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
73
149
  end
74
150
  end
75
151
  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
@@ -1,21 +1,32 @@
1
1
  module Danger
2
2
  class Dangerfile
3
3
  module DSL
4
- # Protect a file from being changed. This can
5
- # be used in combination with some kind of
6
- # permission check if a user is inside the org
7
- def protect_files(path: nil, message: nil, fail_build: true)
8
- broken_rule = false
9
- Dir.glob(path) do |current|
10
- broken_rule = true if self.env.scm.modified_files.include?(current)
11
- end
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
12
14
 
13
- return unless broken_rule
15
+ return unless broken_rule
16
+
17
+ if fail_build
18
+ @dsl.errors << message
19
+ else
20
+ @dsl.warnings << message
21
+ end
22
+ end
14
23
 
15
- if fail_build
16
- self.errors << message
17
- else
18
- self.messages << message
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(" ")
19
30
  end
20
31
  end
21
32
  end
@@ -28,7 +28,7 @@ module Danger
28
28
  end
29
29
 
30
30
  def markdown_parser
31
- @markdown_parser ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
31
+ @markdown_parser ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: true)
32
32
  end
33
33
 
34
34
  def fetch_details
@@ -77,16 +77,28 @@ module Danger
77
77
  end
78
78
 
79
79
  # Sending data to GitHub
80
- def update_pull_request!(warnings: nil, errors: nil, messages: nil)
80
+ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [])
81
81
  comment_result = {}
82
82
 
83
- if (warnings + errors + messages).empty?
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?
84
94
  # Just remove the comment, if there's nothing to say.
85
95
  delete_old_comments!
86
96
  else
87
- issues = client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
88
- editable_issues = issues.reject { |issue| issue[:body].include?("generated_by_danger") == false }
89
- body = generate_comment(warnings: warnings, errors: errors, messages: messages)
97
+ body = generate_comment(warnings: warnings,
98
+ errors: errors,
99
+ messages: messages,
100
+ markdowns: markdowns,
101
+ previous_violations: previous_violations)
90
102
 
91
103
  if editable_issues.empty?
92
104
  comment_result = client.add_comment(ci_source.repo_slug, ci_source.pull_request_id, body)
@@ -108,7 +120,7 @@ module Danger
108
120
  message = generate_github_description(warnings: warnings, errors: errors)
109
121
  client.create_status(ci_source.repo_slug, latest_pr_commit_ref, status, {
110
122
  description: message,
111
- context: "KrauseFx/danger",
123
+ context: "danger/danger",
112
124
  target_url: details_url
113
125
  })
114
126
  rescue
@@ -133,11 +145,15 @@ module Danger
133
145
  end
134
146
  end
135
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
+
136
154
  def generate_github_description(warnings: nil, errors: nil)
137
155
  if errors.empty? && warnings.empty?
138
- compliment = ["Well done.", "Congrats.", "Woo!",
139
- "Yay.", "Jolly good show.", "Good on 'ya.", "Nice work."]
140
- return "All green. #{compliment.sample}"
156
+ return "All green. #{random_compliment}"
141
157
  else
142
158
  message = "⚠ "
143
159
  message += "#{errors.count} Error#{errors.count == 1 ? '' : 's'}. " unless errors.empty?
@@ -147,7 +163,7 @@ module Danger
147
163
  end
148
164
  end
149
165
 
150
- def generate_comment(warnings: [], errors: [], messages: [])
166
+ def generate_comment(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: {})
151
167
  require 'erb'
152
168
 
153
169
  md_template = File.join(Danger.gem_path, "lib/danger/comment_generators/github.md.erb")
@@ -155,18 +171,64 @@ module Danger
155
171
  # erb: http://www.rrn.dk/rubys-erb-templating-system
156
172
  # for the extra args: http://stackoverflow.com/questions/4632879/erb-template-removing-the-trailing-line
157
173
  @tables = [
158
- { name: "Error", emoji: "no_entry_sign", content: errors.map { |s| process_markdown(s) } },
159
- { name: "Warning", emoji: "warning", content: warnings.map { |s| process_markdown(s) } },
160
- { name: "Message", emoji: "book", content: messages.map { |s| process_markdown(s) } }
174
+ table("Error", "no_entry_sign", errors, previous_violations),
175
+ table("Warning", "warning", warnings, previous_violations),
176
+ table("Message", "book", messages, previous_violations)
161
177
  ]
178
+ @markdowns = markdowns
179
+
162
180
  return ERB.new(File.read(md_template), 0, "-").result(binding)
163
181
  end
164
182
 
165
- def process_markdown(string)
166
- html = markdown_parser.render(string)
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)
167
229
  match = html.match(%r{^<p>(.*)</p>$})
168
- return match.captures.first unless match.nil?
169
- html
230
+ message = match.nil? ? html : match.captures.first
231
+ Violation.new(message, violation.sticky)
170
232
  end
171
233
  end
172
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
@@ -16,15 +16,15 @@ module Danger
16
16
  end
17
17
 
18
18
  def added_files
19
- @diff.select { |diff| diff.type == "new" }.map(&:path)
19
+ Danger::FileList.new(@diff.select { |diff| diff.type == "new" }.map(&:path))
20
20
  end
21
21
 
22
22
  def deleted_files
23
- @diff.select { |diff| diff.type == "deleted" }.map(&:path)
23
+ Danger::FileList.new(@diff.select { |diff| diff.type == "deleted" }.map(&:path))
24
24
  end
25
25
 
26
26
  def modified_files
27
- @diff.stats[:files].keys
27
+ Danger::FileList.new(@diff.stats[:files].keys)
28
28
  end
29
29
 
30
30
  def lines_of_code
@@ -79,7 +79,7 @@ module Danger
79
79
  lines = contents.lines
80
80
  indent = ' # '
81
81
  indicator = indent.tr('#', '>')
82
- first_line = (line_numer.zero?)
82
+ first_line = line_numer.zero?
83
83
  last_line = (line_numer == (lines.count - 1))
84
84
 
85
85
  m << "\n"
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "0.6.5"
3
- DESCRIPTION = "Automate your PR etiquette."
2
+ VERSION = "0.7.0".freeze
3
+ DESCRIPTION = "Automate your PR etiquette.".freeze
4
4
  end
@@ -0,0 +1,10 @@
1
+ module Danger
2
+ class Violation
3
+ attr_accessor :message, :sticky
4
+
5
+ def initialize(message, sticky)
6
+ self.message = message
7
+ self.sticky = sticky
8
+ end
9
+ end
10
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: danger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Orta Therox
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-03-14 00:00:00.000000000 Z
12
+ date: 2016-04-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: claide
@@ -185,14 +185,14 @@ dependencies:
185
185
  requirements:
186
186
  - - "~>"
187
187
  - !ruby/object:Gem::Version
188
- version: 0.35.1
188
+ version: 0.38.0
189
189
  type: :development
190
190
  prerelease: false
191
191
  version_requirements: !ruby/object:Gem::Requirement
192
192
  requirements:
193
193
  - - "~>"
194
194
  - !ruby/object:Gem::Version
195
- version: 0.35.1
195
+ version: 0.38.0
196
196
  description: Create a Dangerfile to introspect your pull request in CI, makes it easy
197
197
  to enforce social conventions like changelogs and tests.
198
198
  email:
@@ -207,6 +207,7 @@ files:
207
207
  - README.md
208
208
  - bin/danger
209
209
  - lib/assets/DangerfileTemplate
210
+ - lib/assets/PluginTemplate.rb.template
210
211
  - lib/danger.rb
211
212
  - lib/danger/available_values.rb
212
213
  - lib/danger/ci_source/buildkite.rb
@@ -219,16 +220,21 @@ files:
219
220
  - lib/danger/commands/init.rb
220
221
  - lib/danger/commands/init_helpers/interviewer.rb
221
222
  - lib/danger/commands/local.rb
223
+ - lib/danger/commands/new_plugin.rb
222
224
  - lib/danger/commands/runner.rb
223
225
  - lib/danger/comment_generators/github.md.erb
226
+ - lib/danger/core_ext/string.rb
224
227
  - lib/danger/dangerfile.rb
225
228
  - lib/danger/dangerfile_dsl.rb
226
229
  - lib/danger/environment_manager.rb
230
+ - lib/danger/plugins/plugin.rb
227
231
  - lib/danger/plugins/protect_files.rb
228
232
  - lib/danger/request_sources/github.rb
233
+ - lib/danger/scm_source/file_list.rb
229
234
  - lib/danger/scm_source/git_repo.rb
230
235
  - lib/danger/standard_error.rb
231
236
  - lib/danger/version.rb
237
+ - lib/danger/violation.rb
232
238
  homepage: http://github.com/danger/danger
233
239
  licenses:
234
240
  - MIT
@@ -249,9 +255,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
249
255
  version: '0'
250
256
  requirements: []
251
257
  rubyforge_project:
252
- rubygems_version: 2.2.2
258
+ rubygems_version: 2.4.6
253
259
  signing_key:
254
260
  specification_version: 4
255
261
  summary: Automate your PR etiquette.
256
262
  test_files: []
257
- has_rdoc: