danger 0.7.2 → 0.8.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -13
  3. data/lib/danger/ci_source/circle.rb +1 -1
  4. data/lib/danger/ci_source/drone.rb +18 -0
  5. data/lib/danger/ci_source/local_git_repo.rb +11 -2
  6. data/lib/danger/ci_source/semaphore.rb +18 -0
  7. data/lib/danger/commands/init.rb +8 -8
  8. data/lib/danger/commands/local.rb +26 -8
  9. data/lib/danger/commands/new_plugin.rb +0 -8
  10. data/lib/danger/commands/runner.rb +32 -18
  11. data/lib/danger/comment_generators/github.md.erb +1 -1
  12. data/lib/danger/{scm_source → core_ext}/file_list.rb +1 -0
  13. data/lib/danger/core_ext/string.rb +4 -0
  14. data/lib/danger/danger_core/dangerfile.rb +217 -0
  15. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  16. data/lib/danger/{environment_manager.rb → danger_core/environment_manager.rb} +16 -8
  17. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +69 -0
  18. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +68 -0
  19. data/lib/danger/danger_core/plugins/dangerfile_import_plugin.rb +58 -0
  20. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +87 -0
  21. data/lib/danger/{standard_error.rb → danger_core/standard_error.rb} +1 -1
  22. data/lib/danger/plugin_support/plugin.rb +31 -0
  23. data/lib/danger/plugin_support/plugin_parser.rb +70 -0
  24. data/lib/danger/{request_sources → request_source}/github.rb +6 -37
  25. data/lib/danger/scm_source/git_repo.rb +2 -25
  26. data/lib/danger/version.rb +1 -1
  27. data/lib/danger.rb +3 -4
  28. metadata +79 -17
  29. data/lib/danger/available_values.rb +0 -28
  30. data/lib/danger/dangerfile.rb +0 -123
  31. data/lib/danger/dangerfile_dsl.rb +0 -159
  32. data/lib/danger/plugin.rb +0 -26
  33. data/lib/danger/plugins/protect_files.rb +0 -34
  34. /data/lib/danger/{circle_api.rb → ci_source/circle_api.rb} +0 -0
  35. /data/lib/danger/{violation.rb → danger_core/violation.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bdced6826bdfbe8f06cdc77e9f6b58336140e17c
4
- data.tar.gz: 669ebaaf2d49f985c70a485018ff4bf6e8f4360f
3
+ metadata.gz: f195902ac5ec8dbc7c67059f6484d9c548fa1b9b
4
+ data.tar.gz: af4335f0b4691d17bc27b801b17885f01e9ead07
5
5
  SHA512:
6
- metadata.gz: adbef18ddcb2de50eb915991a82334bf8e559c1b6af8cc00230d8038a1440e09d268f1380aff7936411b9dd6c5d26bef8ae17f10f1a09f9b5a02c42d6b096b7e
7
- data.tar.gz: c91f0a03be03e79d026b6b64c2da6bcc486eeb87883c00fe13d3869b64477bef72c121dd3b8f53c7e724fd341bd2285b884a1b2a16334148b1b960104a21e552
6
+ metadata.gz: 9948b94a2189dd1e4f131f2f0e2265aa666322edfb59f4df8c741d91296f01d684059f11ef67878f6957532f6edba115fbd16071c8b48db01924d36a08584dbe
7
+ data.tar.gz: 44c3f8b65bd973eb45175b0b494e07749508e91514e55e8179785edcd567fffc736974eeacda00f850671761e72607463788b70ad6e1bc865e2d821c02950f9b
data/README.md CHANGED
@@ -8,12 +8,11 @@ Formalize your Pull Request etiquette.
8
8
  -------
9
9
 
10
10
  <p align="center">
11
- <a href="#installation">Installation</a> &bull;
12
- <a href="#usage">Usage</a> &bull;
11
+ <a href="#getting-started">Getting Started</a> &bull;
12
+ <a href="#usage-on-ci">Usage</a> &bull;
13
13
  <a href="#dsl">DSL</a> &bull;
14
- <a href="#constraints">Constraints</a> &bull;
15
- <a href="#advanced">Advanced</a> &bull;
16
- <a href="#contributing">Contributing</a>
14
+ <a href="#plugins">Plugins</a> &bull;
15
+ <a href="#test-locally-with-danger-local">Local</a>
17
16
  </p>
18
17
 
19
18
  -------
@@ -32,6 +31,8 @@ To get up and running quickly, just run
32
31
  bundle exec danger init
33
32
  ```
34
33
 
34
+ The guide will create a default `Dangerfile` and take you through the process of setting up an account for feedback on GitHub.
35
+
35
36
  ## Usage on CI
36
37
 
37
38
  ```
@@ -40,6 +41,10 @@ bundle exec danger
40
41
 
41
42
  This will look at your `Dangerfile` and update the pull request accordingly. While you are setting up Danger, you may want to use: `--verbose` for more debug information.
42
43
 
44
+ ## CI Support
45
+
46
+ Danger currently is supported on Travis CI, Circle CI, Xcode Bots via Buildasaur, BuildKite, Jenkins, Semaphore, and Drone CI. These work via environment variables, so it's easy to extend to include your own.
47
+
43
48
  ## What happens?
44
49
 
45
50
  Danger runs at the end of a CI build, she will execute a `Dangerfile`. This file is given some special variables based on the git diff and the Pull Request being running. You can use these variables in Ruby to provide messages, warnings and failures for your build. You set up Danger with a GitHub user account and she will post updates via comments on the Pull Request, and can fail your build too.
@@ -113,7 +118,7 @@ Command | Description
113
118
 
114
119
  ## Plugins
115
120
 
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.
121
+ Danger was built with a platform in mind: she can be used with any kind of software project and allows you to write your own action to have structured source code.
117
122
 
118
123
  In your `Dangerfile` you can import local or remote actions using
119
124
 
@@ -134,11 +139,7 @@ To create a new plugin run
134
139
  danger new_plugin
135
140
  ```
136
141
 
137
- This will generate a new Ruby file which you can modify to fit your needs.
138
-
139
- ## Support
140
-
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.
142
+ This will generate a new Ruby file which you can modify to fit your needs.
142
143
 
143
144
  ## Advanced
144
145
 
@@ -155,8 +156,24 @@ open to turning useful bits into the official API.
155
156
 
156
157
  ## Test locally with `danger local`
157
158
 
158
- Using `danger local` will look for the last merged pull request in your git history, and apply your current
159
- `Dangerfile` against that Pull Request. Useful when editing.
159
+ You can use `danger local` to run Danger in an environment similar to how she will be ran on CI. By default Danger will look
160
+ at the most recently merged PR, then run your `Dangerfile` against that Pull Request. This is really useful when making changes.
161
+
162
+ If you have a specific PR in mind that you'd like to work against, make sure you have it merged in your current git
163
+ history, then append `--use-merged-pr=[id]` to the command.
164
+
165
+ ### Adding a new CI
166
+ If the CI server you're using isn't available yet, you can build it yourself:
167
+
168
+ Take a look at some of the [already existing integrations](https://github.com/danger/danger/tree/master/lib/danger/ci_source). The class has 2 mandatory methods:
169
+
170
+ - `self.validates?` which should detect if the CI is active (detecting via ENV variables, mostly)
171
+ - `initialize` which should set 2 variables:
172
+ - `self.repo_slug` the repo slug, in `org/repo` or `user/repo` format.
173
+ - `self.pull_request_id` the number of the pull request that the CI is testing (often available in ENV variables)
174
+
175
+ We'd love to see pull requests for new integrations!
176
+
160
177
 
161
178
  ## Suppress Violations
162
179
 
@@ -182,6 +199,11 @@ fail("PR needs labels", sticky: false) if pr_labels.empty?
182
199
 
183
200
  Here are some real-world Dangerfiles: [artsy/eigen](https://github.com/artsy/eigen/blob/master/Dangerfile), [danger/danger](https://github.com/danger/danger/blob/master/Dangerfile), [artsy/elan](https://github.com/artsy/elan/blob/master/Dangerfile) and more!
184
201
 
202
+ ## Usage with GitHub Enterprise
203
+ Danger allows usage with GitHub Enterprise by setting 2 environment variables:
204
+ - `DANGER_GITHUB_HOST` to the host that GitHub is running on
205
+ - `DANGER_GITHUB_API_HOST` to the host that the GitHub Enterprise API is reachable on.
206
+
185
207
  ## License, Contributor's Guidelines and Code of Conduct
186
208
 
187
209
  [Join our Slack Group](https://danger-slack.herokuapp.com/)
@@ -1,6 +1,6 @@
1
1
  # https://circleci.com/docs/environment-variables
2
2
  require 'uri'
3
- require 'danger/circle_api'
3
+ require 'danger/ci_source/circle_api'
4
4
 
5
5
  module Danger
6
6
  module CISource
@@ -0,0 +1,18 @@
1
+ # http://readme.drone.io/usage/variables/
2
+
3
+ module Danger
4
+ module CISource
5
+ class Drone < CI
6
+ def self.validates?(env)
7
+ return !env["DRONE"].nil?
8
+ end
9
+
10
+ def initialize(env)
11
+ self.repo_slug = env["DRONE_REPO"]
12
+ if env["DRONE_PULL_REQUEST"].to_i > 0
13
+ self.pull_request_id = env["DRONE_PULL_REQUEST"]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -34,10 +34,19 @@ module Danger
34
34
  end
35
35
  end
36
36
 
37
+ specific_pr = env["LOCAL_GIT_PR_ID"]
38
+ pr_ref = specific_pr ? "##{specific_pr}" : ''
39
+ pr_command = "log --merges --oneline | grep \"Merge pull request #{pr_ref}\" | head -n 1"
40
+
37
41
  # get the most recent PR merge
38
- pr_merge = run_git "log --since='2 weeks ago' --merges --oneline | grep \"Merge pull request\" | head -n 1".strip
42
+ pr_merge = run_git pr_command.strip
43
+
39
44
  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
+ if specific_pr
46
+ raise "Could not find the pull request (#{specific_pr}) inside the git history for this repo."
47
+ else
48
+ raise "No recent pull requests found for this repo, danger requires at least one PR for the local mode."
49
+ end
41
50
  end
42
51
 
43
52
  self.pull_request_id = pr_merge.match("#([0-9]+)")[1]
@@ -0,0 +1,18 @@
1
+ # https://semaphoreci.com/docs/available-environment-variables.html
2
+
3
+ module Danger
4
+ module CISource
5
+ class Semaphore < CI
6
+ def self.validates?(env)
7
+ return !env["SEMAPHORE"].nil?
8
+ end
9
+
10
+ def initialize(env)
11
+ self.repo_slug = env["SEMAPHORE_REPO_SLUG"]
12
+ if env["PULL_REQUEST_NUMBER"].to_i > 0
13
+ self.pull_request_id = env["PULL_REQUEST_NUMBER"]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -23,7 +23,7 @@ module Danger
23
23
 
24
24
  def run
25
25
  ui = Interviewer.new
26
- ui.say "\nOK, thanks #{ENV['LOGNAME']}, grab a seat and we'll get you started.\n".yellow
26
+ ui.say "\nOK, thanks #{ENV['LOGNAME']}, have a seat and we'll get you started.\n".yellow
27
27
  ui.pause 1
28
28
 
29
29
  show_todo_state
@@ -43,11 +43,11 @@ module Danger
43
43
  end
44
44
 
45
45
  def show_todo_state
46
- ui.say "We need to do the following steps:\n"
46
+ ui.say "We need to do the following:\n"
47
47
  ui.pause 0.6
48
48
  ui.say " - [ ] Create a Dangerfile and add a few simple rules."
49
49
  ui.pause 0.6
50
- ui.say " - [#{@account_created ? 'x' : ' '}] Create a GitHub account for Danger use for messaging."
50
+ ui.say " - [#{@account_created ? 'x' : ' '}] Create a GitHub account for Danger to use, for messaging."
51
51
  ui.pause 0.6
52
52
  ui.say " - [ ] Set up an access token for Danger."
53
53
  ui.pause 0.6
@@ -71,7 +71,7 @@ module Danger
71
71
  ui.pause 2
72
72
 
73
73
  ui.say "There's a collection of small, simple ideas in here, but Danger is about being able to easily"
74
- ui.say "iterate. The power comes from you have the ability to codify fixes to some of the problems"
74
+ ui.say "iterate. The power comes from you having the ability to codify fixes for some of the problems"
75
75
  ui.say "that come up in day to day programming. It can be difficult to try and see those from day 1."
76
76
 
77
77
  ui.say "\nIf you'd like to investigate the file, and make some changes - I'll wait here,"
@@ -113,7 +113,7 @@ module Danger
113
113
  ui.say "For Open Source projects, I'd recommend giving the token the smallest scope possible."
114
114
  ui.say "This means only providing access to " + "public_repo".yellow + " in the token.\n\n"
115
115
  ui.pause 1
116
- ui.say "This token limits Danger's abilities to just to writing comments on OSS projects. I recommend"
116
+ ui.say "This token limits Danger's abilities to just writing comments on OSS projects. I recommend"
117
117
  ui.say "this because the token can quite easily be extracted from the environment via pull requests."
118
118
  ui.say "#{@bot_name} does not need admin access to your repo. So its ability to cause chaos is minimalized.\n"
119
119
 
@@ -148,7 +148,7 @@ module Danger
148
148
  ui.say "\nOK, I'll give you a moment to do this..."
149
149
  ui.wait_for_return
150
150
 
151
- ui.say "Final step: exposing the GitHub token as an environment build variable."
151
+ ui.header "Final step: exposing the GitHub token as an environment build variable."
152
152
  ui.pause 0.4
153
153
  if considered_an_oss_repo?
154
154
  ui.say "As you have an Open Source repo, this token should be considered public, otherwise you cannot"
@@ -207,7 +207,7 @@ module Danger
207
207
  # https://travis-ci.org/artsy/eigen/settings
208
208
  ui.say "In order to add an environment variable, go to:"
209
209
  ui.link "https://travis-ci.org/#{current_repo_slug}/settings"
210
- ui.say "\nThe name is " + "DANGER_GITHUB_API_TOKEN".yellow + " and the value is the GitHub Personal Acess Token."
210
+ ui.say "\nThe name is " + "DANGER_GITHUB_API_TOKEN".yellow + " and the value is the GitHub Personal Access Token."
211
211
  if @is_open_source
212
212
  ui.say "Make sure to have \"Display value in build log\" enabled."
213
213
  end
@@ -262,7 +262,7 @@ module Danger
262
262
  ui.say "\n\n🎉"
263
263
  ui.pause 0.6
264
264
 
265
- ui.say "And you're set. Danger is a collaboration between Orta Therox, Gem 'Danger' McShane and Felix Krause."
265
+ ui.say "And you're good to go. Danger is a collaboration between Orta Therox, Gem 'Danger' McShane and Felix Krause."
266
266
  ui.say "If you like it, let others know. If you want to know more, follow " + "@orta".yellow + " and " + "@KrauseFx".yellow + " on Twitter."
267
267
  ui.say "If you don't like it, help us improve it! xxx"
268
268
  end
@@ -5,9 +5,16 @@ module Danger
5
5
 
6
6
  def initialize(argv)
7
7
  @dangerfile_path = "Dangerfile" if File.exist? "Dangerfile"
8
+ @pr_num = argv.option('use-merged-pr')
8
9
  super
9
10
  end
10
11
 
12
+ def self.options
13
+ [
14
+ ['--use-merged-pr=[#id]', 'The ID of an already merged PR inside your history to use as a reference for the local run.']
15
+ ].concat(super)
16
+ end
17
+
11
18
  def validate!
12
19
  super
13
20
  unless @dangerfile_path
@@ -17,9 +24,11 @@ module Danger
17
24
 
18
25
  def run
19
26
  ENV["DANGER_USE_LOCAL_GIT"] = "YES"
27
+ ENV["LOCAL_GIT_PR_ID"] = @pr_num if @pr_num
20
28
 
21
- dm = Dangerfile.new
22
- dm.env = EnvironmentManager.new(ENV)
29
+ env = EnvironmentManager.new(ENV)
30
+ dm = Dangerfile.new(env)
31
+ dm.init_plugins
23
32
 
24
33
  source = dm.env.ci_source
25
34
  if source.nil? or source.repo_slug.empty?
@@ -41,15 +50,24 @@ module Danger
41
50
  # We can use tokenless here, as it's running on someone's computer
42
51
  # and is IP locked, as opposed to on the CI.
43
52
  gh.support_tokenless_auth = true
44
- gh.fetch_details
45
53
 
46
- dm.env.request_source = gh
54
+ begin
55
+ gh.fetch_details
56
+ rescue Octokit::NotFound
57
+ puts "Local repository was not found on GitHub. If you're trying to test a private repository please provide a valid API token through " + "DANGER_GITHUB_API_TOKEN".yellow + " environment variable."
58
+ return
59
+ end
47
60
 
48
- dm.env.scm = GitRepo.new
61
+ dm.env.request_source = gh
49
62
 
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))
52
- dm.print_results
63
+ begin
64
+ dm.env.ensure_danger_branches_are_setup
65
+ dm.env.scm.diff_for_folder(".", from: dm.env.ci_source.base_commit, to: dm.env.ci_source.head_commit)
66
+ dm.parse(Pathname.new(@dangerfile_path))
67
+ dm.print_results
68
+ ensure
69
+ dm.env.clean_up
70
+ end
53
71
  end
54
72
  end
55
73
  end
@@ -3,14 +3,6 @@ module Danger
3
3
  self.summary = 'Generate a new danger plugin.'
4
4
  self.command = 'new_plugin'
5
5
 
6
- def initialize(argv)
7
- super
8
- end
9
-
10
- def validate!
11
- super
12
- end
13
-
14
6
  def run
15
7
  require 'fileutils'
16
8
 
@@ -6,9 +6,13 @@ module Danger
6
6
 
7
7
  self.summary = 'Run the Dangerfile.'
8
8
  self.command = 'danger'
9
+ self.version = Danger::VERSION
10
+
11
+ self.plugin_prefixes = %w(claide danger)
9
12
 
10
13
  def initialize(argv)
11
- @dangerfile_path = "Dangerfile" if File.exist? "Dangerfile"
14
+ dangerfile = argv.option('dangerfile', 'Dangerfile')
15
+ @dangerfile_path = dangerfile if File.exist? dangerfile
12
16
  @base = argv.option('base')
13
17
  @head = argv.option('head')
14
18
  super
@@ -24,37 +28,47 @@ module Danger
24
28
  def self.options
25
29
  [
26
30
  ['--base=[master|dev|stable]', 'A branch/tag/commit to use as the base of the diff'],
27
- ['--head=[master|dev|stable]', 'A branch/tag/commit to use as the head']
31
+ ['--head=[master|dev|stable]', 'A branch/tag/commit to use as the head'],
32
+ ['--dangerfile=<path/to/dangerfile>', 'The location of your Dangerfile']
28
33
  ].concat(super)
29
34
  end
30
35
 
31
36
  def run
32
- # The order of the following commands is *really* important
33
- dm = Dangerfile.new
34
- dm.verbose = verbose
35
- dm.env = EnvironmentManager.new(ENV)
36
- return unless dm.env.ci_source # if it's not a PR
37
+ env = EnvironmentManager.new(ENV)
38
+ dm = Dangerfile.new(env)
37
39
 
38
- dm.env.fill_environment_vars
39
- dm.env.ensure_danger_branches_are_setup
40
+ if dm.env.pr?
41
+ dm.verbose = verbose
42
+ dm.init_plugins
40
43
 
41
- # Offer the chance for a user to specify a branch through the command line
42
- ci_base = @base || dm.env.danger_head_branch
43
- ci_head = @head || dm.env.danger_base_branch
44
- dm.env.scm.diff_for_folder(".", from: ci_base, to: ci_head)
44
+ dm.env.fill_environment_vars
45
45
 
46
- dm.parse Pathname.new(@dangerfile_path)
46
+ begin
47
+ dm.env.ensure_danger_branches_are_setup
47
48
 
48
- post_results(dm)
49
+ # Offer the chance for a user to specify a branch through the command line
50
+ ci_base = @base || dm.env.danger_head_branch
51
+ ci_head = @head || dm.env.danger_base_branch
52
+ dm.env.scm.diff_for_folder(".", from: ci_base, to: ci_head)
49
53
 
50
- dm.env.clean_up
54
+ dm.parse Pathname.new(@dangerfile_path)
51
55
 
52
- dm.print_results
56
+ post_results dm
57
+ dm.print_results
58
+ ensure
59
+ dm.env.clean_up
60
+ end
61
+ else
62
+ puts "Not a Pull Request - skipping `danger` run"
63
+ end
53
64
  end
54
65
 
55
66
  def post_results(dm)
56
67
  gh = dm.env.request_source
57
- gh.update_pull_request!(warnings: dm.warnings, errors: dm.errors, messages: dm.messages, markdowns: dm.markdowns)
68
+ violations = dm.violation_report
69
+ status = dm.status_report
70
+
71
+ gh.update_pull_request!(warnings: violations[:warnings], errors: violations[:errors], messages: violations[:messages], markdowns: status[:markdowns])
58
72
  end
59
73
  end
60
74
  end
@@ -36,6 +36,6 @@
36
36
  <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
37
37
  <% end %>
38
38
 
39
- <p align="right" data-meta="generated_by_danger" data-base-commit="<%= @base_commit %>" data-head-commit="<%= @head_commit %>" >
39
+ <p align="right" data-meta="generated_by_danger">
40
40
  Generated by :no_entry_sign: <a href="https://github.com/danger/danger/">danger</a>
41
41
  </p>
@@ -2,6 +2,7 @@ module Danger
2
2
  class FileList < Array
3
3
  # Information about pattern: http://ruby-doc.org/core-2.2.0/File.html#method-c-fnmatch
4
4
  # e.g. "**/something.*" for any file called something with any extension
5
+
5
6
  def include?(pattern)
6
7
  self.each do |current|
7
8
  return true if File.fnmatch(pattern, current)
@@ -3,6 +3,10 @@ class String
3
3
  split('_').collect!(&:capitalize).join
4
4
  end
5
5
 
6
+ def danger_pluralize(count)
7
+ "#{count} #{self}#{'s' unless count == 1}"
8
+ end
9
+
6
10
  def danger_underscore
7
11
  self.gsub(/::/, '/').
8
12
  gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
@@ -0,0 +1,217 @@
1
+ # So much was ripped direct from CocoaPods-Core - thanks!
2
+
3
+ require 'danger/danger_core/dangerfile_dsl'
4
+ require 'danger/danger_core/standard_error'
5
+
6
+ require 'danger/danger_core/plugins/dangerfile_messaging_plugin'
7
+ require 'danger/danger_core/plugins/dangerfile_import_plugin'
8
+ require 'danger/danger_core/plugins/dangerfile_git_plugin'
9
+ require 'danger/danger_core/plugins/dangerfile_github_plugin'
10
+
11
+ require 'danger/danger_core/plugins/dangerfile_github_plugin'
12
+
13
+ module Danger
14
+ class Dangerfile
15
+ include Danger::Dangerfile::DSL
16
+
17
+ attr_accessor :env, :verbose, :plugins
18
+
19
+ # @return [Pathname] the path where the Dangerfile was loaded from. It is nil
20
+ # if the Dangerfile was generated programmatically.
21
+ #
22
+ attr_accessor :defined_in_file
23
+
24
+ # @return [String] a string useful to represent the Dangerfile in a message
25
+ # presented to the user.
26
+ #
27
+ def to_s
28
+ 'Dangerfile'
29
+ end
30
+
31
+ # These are the classes that are allowed to also use method_missing
32
+ # in order to provide broader plugin support
33
+ def core_plugin_classes
34
+ [
35
+ Danger::DangerfileMessagingPlugin,
36
+ Danger::DangerfileImportPlugin,
37
+ Danger::DangerfileGitHubPlugin,
38
+ Danger::DangerfileGitPlugin
39
+ ]
40
+ end
41
+
42
+ # Both of these methods exist on all objects
43
+ # http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-warn
44
+ # http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-fail
45
+ # However, as we're using using them in the DSL, they won't
46
+ # get method_missing called correctly.
47
+
48
+ def warn(*args, &blk)
49
+ method_missing(:warn, *args, &blk)
50
+ end
51
+
52
+ def fail(*args, &blk)
53
+ method_missing(:fail, *args, &blk)
54
+ end
55
+
56
+ # When an undefined method is called, we check to see if it's something
57
+ # that the DSLs have, then starts looking at plugins support.
58
+ def method_missing(method_sym, *arguments, &_block)
59
+ @core_plugins.each do |plugin|
60
+ if plugin.public_methods(false).include?(method_sym)
61
+ return plugin.send(method_sym, *arguments)
62
+ end
63
+ end
64
+ super
65
+ end
66
+
67
+ def initialize(env_manager)
68
+ @plugins = {}
69
+ @core_plugins = []
70
+
71
+ # Triggers the core plugins
72
+ @env = env_manager
73
+
74
+ # Triggers local plugins from the root of a project
75
+ Dir["./danger_plugins/*.rb"].each do |file|
76
+ require File.expand_path(file)
77
+ end
78
+
79
+ refresh_plugins if env_manager.pr?
80
+ end
81
+
82
+ # Iterate through available plugin classes and initialize them with
83
+ # a reference to this Dangerfile
84
+ def refresh_plugins
85
+ plugins = ObjectSpace.each_object(Class).select { |klass| klass < Danger::Plugin }
86
+ plugins.each do |klass|
87
+ next if klass.singleton_class?
88
+ plugin = klass.new(self)
89
+ next if plugin.nil? || @plugins[klass]
90
+
91
+ name = plugin.class.instance_name
92
+ self.class.send(:attr_reader, name)
93
+ instance_variable_set("@#{name}", plugin)
94
+
95
+ @plugins[klass] = plugin
96
+ @core_plugins << plugin if core_plugin_classes.include? klass
97
+ end
98
+ end
99
+ alias init_plugins refresh_plugins
100
+
101
+ def core_dsl_attributes
102
+ @core_plugins.map { |plugin| { plugin: plugin, methods: plugin.public_methods(false) } }
103
+ end
104
+
105
+ def external_dsl_attributes
106
+ plugins.values.reject { |plugin| @core_plugins.include? plugin } .map { |plugin| { plugin: plugin, methods: plugin.public_methods(false) } }
107
+ end
108
+
109
+ def method_values_for_plugin_hashes(plugin_hashes)
110
+ plugin_hashes.flat_map do |plugin_hash|
111
+ plugin = plugin_hash[:plugin]
112
+ methods = plugin_hash[:methods].reject { |name| plugin.method(name).arity != 0 }
113
+
114
+ methods.map do |method|
115
+ value = plugin.send(method)
116
+ value = value.scan(/.{,80}/).to_a.each(&:strip!).join("\n") if method == :pr_body
117
+
118
+ # So that we either have one value per row
119
+ # or we have [] for an empty array
120
+ value = value.join("\n") if value.kind_of?(Array) && value.count > 0
121
+ [method.to_s, value]
122
+ end
123
+ end
124
+ end
125
+
126
+ # Iterates through the DSL's attributes, and table's the output
127
+ def print_known_info
128
+ rows = []
129
+ rows += method_values_for_plugin_hashes(core_dsl_attributes)
130
+ rows << ["---", "---"]
131
+ rows += method_values_for_plugin_hashes(external_dsl_attributes)
132
+ rows << ["---", "---"]
133
+ rows << ["SCM", env.scm.class]
134
+ rows << ["Source", env.ci_source.class]
135
+ rows << ["Requests", env.request_source.class]
136
+ rows << ["Base Commit", env.meta_info_for_base]
137
+ rows << ["Head Commit", env.meta_info_for_head]
138
+
139
+ params = {}
140
+ params[:rows] = rows.each { |current| current[0] = current[0].yellow }
141
+ params[:title] = "Danger v#{Danger::VERSION}\nDSL Attributes".green
142
+
143
+ puts ""
144
+ puts Terminal::Table.new(params)
145
+ puts ""
146
+ end
147
+
148
+ # Parses the file at a path, optionally takes the content of the file for DI
149
+ #
150
+ def parse(path, contents = nil)
151
+ print_known_info if verbose
152
+
153
+ contents ||= File.open(path, 'r:utf-8', &:read)
154
+
155
+ # Work around for Rubinius incomplete encoding in 1.9 mode
156
+ if contents.respond_to?(:encoding) && contents.encoding.name != 'UTF-8'
157
+ contents.encode!('UTF-8')
158
+ end
159
+
160
+ if contents.tr!('“”‘’‛', %(""'''))
161
+ # Changes have been made
162
+ puts "Your #{path.basename} has had smart quotes sanitised. " \
163
+ 'To avoid issues in the future, you should not use ' \
164
+ 'TextEdit for editing it. If you are not using TextEdit, ' \
165
+ 'you should turn off smart quotes in your editor of choice.'.red
166
+ end
167
+
168
+ if contents.include?("puts")
169
+ puts "You used `puts` in your Dangerfile. To print out text to GitHub use `message` instead"
170
+ end
171
+
172
+ self.defined_in_file = path
173
+ instance_eval do
174
+ # rubocop:disable Lint/RescueException
175
+ begin
176
+ # rubocop:disable Eval
177
+ eval(contents, nil, path.to_s)
178
+ # rubocop:enable Eval
179
+ rescue Exception => e
180
+ message = "Invalid `#{path.basename}` file: #{e.message}"
181
+ raise DSLError.new(message, path, e.backtrace, contents)
182
+ end
183
+ # rubocop:enable Lint/RescueException
184
+ end
185
+ end
186
+
187
+ def print_results
188
+ status = status_report
189
+ return if (status[:errors] + status[:warnings] + status[:messages] + status[:markdowns]).count == 0
190
+
191
+ puts ""
192
+ puts "danger results:"
193
+ [:errors, :warnings, :messages].each do |current|
194
+ params = {}
195
+ params[:rows] = status[current].map { |item| [item] }
196
+ next unless params[:rows].count > 0
197
+ params[:title] = case current
198
+ when :errors
199
+ current.to_s.capitalize.red
200
+ when :warnings
201
+ current.to_s.capitalize.yellow
202
+ else
203
+ current.to_s.capitalize
204
+ end
205
+
206
+ puts ""
207
+ puts Terminal::Table.new(params)
208
+ puts ""
209
+ end
210
+
211
+ puts "Markdown: ".green if status[:markdowns].count > 0
212
+ status[:markdowns].each do |current_markdown|
213
+ puts current_markdown
214
+ end
215
+ end
216
+ end
217
+ end