danger 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +28 -23
  3. data/lib/danger.rb +3 -4
  4. data/lib/danger/ci_source/circle.rb +1 -1
  5. data/lib/danger/{circle_api.rb → ci_source/circle_api.rb} +0 -0
  6. data/lib/danger/ci_source/drone.rb +18 -0
  7. data/lib/danger/ci_source/semaphore.rb +18 -0
  8. data/lib/danger/commands/local.rb +19 -10
  9. data/lib/danger/commands/runner.rb +30 -18
  10. data/lib/danger/comment_generators/github.md.erb +1 -1
  11. data/lib/danger/{scm_source → core_ext}/file_list.rb +1 -0
  12. data/lib/danger/danger_core/dangerfile.rb +217 -0
  13. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  14. data/lib/danger/{environment_manager.rb → danger_core/environment_manager.rb} +14 -6
  15. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +69 -0
  16. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +68 -0
  17. data/lib/danger/danger_core/plugins/dangerfile_import_plugin.rb +58 -0
  18. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +87 -0
  19. data/lib/danger/{standard_error.rb → danger_core/standard_error.rb} +1 -1
  20. data/lib/danger/{violation.rb → danger_core/violation.rb} +0 -0
  21. data/lib/danger/plugin_support/plugin.rb +31 -0
  22. data/lib/danger/plugin_support/plugin_parser.rb +70 -0
  23. data/lib/danger/{request_sources → request_source}/github.rb +2 -33
  24. data/lib/danger/scm_source/git_repo.rb +1 -30
  25. data/lib/danger/version.rb +1 -1
  26. metadata +79 -17
  27. data/lib/danger/available_values.rb +0 -29
  28. data/lib/danger/dangerfile.rb +0 -123
  29. data/lib/danger/dangerfile_dsl.rb +0 -159
  30. data/lib/danger/plugin.rb +0 -25
  31. data/lib/danger/plugins/protect_files.rb +0 -34
@@ -0,0 +1,29 @@
1
+ module Danger
2
+ class Dangerfile
3
+ # Anything inside this module is considered public API, and in the future
4
+ # documentation will be generated from it via rdoc.
5
+
6
+ module DSL
7
+ # @!group Danger Zone
8
+ # Provides access to the raw Travis/Circle/Buildkite/GitHub objects, which
9
+ # you can use to pull out extra bits of information. _Warning_
10
+ # the interfaces of these objects is **not** considered a part of the Dangerfile public
11
+ # API, and is viable to change occasionally on the whims of developers.
12
+ # @return [EnvironmentManager]
13
+
14
+ attr_reader :env
15
+
16
+ private
17
+
18
+ def initialize
19
+ load_default_plugins
20
+ end
21
+
22
+ def load_default_plugins
23
+ Dir["./danger_plugins/*.rb"].each do |file|
24
+ require File.expand_path(file)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  require "danger/ci_source/ci_source"
2
- require "danger/request_sources/github"
2
+ require "danger/request_source/github"
3
3
 
4
4
  module Danger
5
5
  class EnvironmentManager
@@ -15,7 +15,6 @@ module Danger
15
15
  if self.ci_source.repo_slug and self.ci_source.pull_request_id
16
16
  break
17
17
  else
18
- puts "Not a Pull Request - skipping `danger` run"
19
18
  self.ci_source = nil
20
19
  return nil
21
20
  end
@@ -25,18 +24,27 @@ module Danger
25
24
 
26
25
  # only GitHub for now, open for PRs adding more!
27
26
  self.request_source = GitHub.new(self.ci_source, ENV)
27
+ # Also Git only for now, also open for PRs adding more!
28
+ self.scm = GitRepo.new # For now
29
+ end
30
+
31
+ def pr?
32
+ self.ci_source != nil
28
33
  end
29
34
 
30
35
  def fill_environment_vars
31
36
  request_source.fetch_details
32
-
33
- self.scm = GitRepo.new # For now
34
37
  end
35
38
 
36
39
  def ensure_danger_branches_are_setup
40
+ clean_up
41
+
37
42
  # As this currently just works with GitHub, we can use a github specific feature here:
38
43
  pull_id = ci_source.pull_request_id
39
- test_branch = request_source.base_commit
44
+
45
+ # TODO: This isn't optimal, should be hidden behind some kind of facade, but the plugin makes that
46
+ # difficult to do without accessing the dangerfile
47
+ test_branch = request_source.pr_json[:base][:sha]
40
48
 
41
49
  # Next, we want to ensure that we have a version of the current branch at a known location
42
50
  scm.exec "branch #{danger_base_branch} #{test_branch}"
@@ -48,7 +56,7 @@ module Danger
48
56
 
49
57
  def clean_up
50
58
  [danger_base_branch, danger_base_branch].each do |branch|
51
- scm.exec "branch -D #{branch}"
59
+ scm.exec("branch -D #{branch}") unless scm.exec("rev-parse --quiet --verify #{branch}").empty?
52
60
  end
53
61
  end
54
62
 
@@ -0,0 +1,69 @@
1
+ require 'danger/plugin_support/plugin'
2
+ require 'danger/core_ext/file_list'
3
+
4
+ module Danger
5
+ class DangerfileGitPlugin < Plugin
6
+ def initialize(dangerfile)
7
+ super(dangerfile)
8
+ raise unless dangerfile.env.scm.class == Danger::GitRepo
9
+
10
+ @git = dangerfile.env.scm
11
+ end
12
+
13
+ # @!group Git Files
14
+ # Paths for files that were added during the diff
15
+ # @return [FileList] an [Array] subclass
16
+ #
17
+ def added_files
18
+ Danger::FileList.new(@git.diff.select { |diff| diff.type == "new" }.map(&:path))
19
+ end
20
+
21
+ # @!group Git Files
22
+ # Paths for files that were removed during the diff
23
+ # @return [FileList] an [Array] subclass
24
+ #
25
+ def deleted_files
26
+ Danger::FileList.new(@git.diff.select { |diff| diff.type == "deleted" }.map(&:path))
27
+ end
28
+
29
+ # @!group Git Files
30
+ # Paths for files that changed during the diff
31
+ # @return [FileList] an [Array] subclass
32
+ #
33
+ def modified_files
34
+ Danger::FileList.new(@git.diff.stats[:files].keys)
35
+ end
36
+
37
+ # @!group Git Metadata
38
+ # The overall lines of code added/removed in the diff
39
+ # @return Int
40
+ #
41
+ def lines_of_code
42
+ @git.diff.lines
43
+ end
44
+
45
+ # @!group Git Metadata
46
+ # The overall lines of code removed in the diff
47
+ # @return Int
48
+ #
49
+ def deletions
50
+ @git.diff.deletions
51
+ end
52
+
53
+ # @!group Git Metadata
54
+ # The overall lines of code added in the diff
55
+ # @return Int
56
+ #
57
+ def insertions
58
+ @git.diff.insertions
59
+ end
60
+
61
+ # @!group Git Metadata
62
+ # The log of commits inside the diff
63
+ # @return [Git::Log] from the gem `git`
64
+ #
65
+ def commits
66
+ @git.log.to_a
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,68 @@
1
+ require 'danger/plugin_support/plugin'
2
+
3
+ module Danger
4
+ class DangerfileGitHubPlugin < Plugin
5
+ def initialize(dangerfile)
6
+ super(dangerfile)
7
+ return nil unless dangerfile.env.request_source.class == Danger::GitHub
8
+
9
+ @github = dangerfile.env.request_source
10
+ end
11
+
12
+ # @!group PR Metadata
13
+ # The title of the Pull Request
14
+ # @return String
15
+ #
16
+ def pr_title
17
+ @github.pr_json[:title].to_s
18
+ end
19
+
20
+ # @!group PR Metadata
21
+ # The body text of the Pull Request
22
+ # @return String
23
+ #
24
+ def pr_body
25
+ @github.pr_json[:body].to_s
26
+ end
27
+
28
+ # @!group PR Metadata
29
+ # The username of the author of the Pull Request
30
+ # @return String
31
+ #
32
+ def pr_author
33
+ @github.pr_json[:user][:login].to_s
34
+ end
35
+
36
+ # @!group PR Metadata
37
+ # The labels assigned to the Pull Request
38
+ # @return [String]
39
+ #
40
+ def pr_labels
41
+ @github.issue_json[:labels].map { |l| l[:name] }
42
+ end
43
+
44
+ # @!group PR Commit Metadata
45
+ # The branch to which the PR is going to be merged into
46
+ # @return String
47
+ #
48
+ def branch_for_merge
49
+ @github.pr_json[:base][:ref]
50
+ end
51
+
52
+ # @!group PR Commit Metadata
53
+ # The base commit to which the PR is going to be merged as a parent
54
+ # @return String
55
+ #
56
+ def base_commit
57
+ @github.pr_json[:base][:sha]
58
+ end
59
+
60
+ # @!group PR Commit Metadata
61
+ # The head commit to which the PR is requesting to be merged from
62
+ # @return String
63
+ #
64
+ def head_commit
65
+ @github.pr_json[:head][:sha]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ require 'danger/plugin_support/plugin'
2
+
3
+ module Danger
4
+ class DangerfileImportPlugin < Plugin
5
+ # @!group Plugins
6
+ # Download a local or remote plugin and use it locally
7
+ #
8
+ # @param [String] path
9
+ # a local path or a https URL to the Ruby file to import
10
+ # a danger plugin from.
11
+ def import(path)
12
+ raise "`import` requires a string" unless path.kind_of?(String)
13
+ path += ".rb" unless path.end_with?(".rb")
14
+
15
+ if path.start_with?("http")
16
+ import_url(path)
17
+ else
18
+ import_local(path)
19
+ end
20
+ end
21
+
22
+ # @!group Plugins
23
+ # Download a remote plugin and use it locally
24
+ #
25
+ # @param [String] url
26
+ # https URL to the Ruby file to use
27
+ def import_url(url)
28
+ raise "URL is not https, for security reasons `danger` only supports encrypted requests" unless url.start_with?("https://")
29
+
30
+ require 'tmpdir'
31
+ require 'faraday'
32
+
33
+ @http_client ||= Faraday.new do |b|
34
+ b.adapter :net_http
35
+ end
36
+ content = @http_client.get(url)
37
+
38
+ Dir.mktmpdir do |dir|
39
+ path = File.join(dir, "temporary_remote_action.rb")
40
+ File.write(path, content.body)
41
+ import_local(path)
42
+ end
43
+ end
44
+
45
+ # @!group Plugins
46
+ # Import one or more local plugins
47
+ #
48
+ # @param [String] path
49
+ # The path to the file to import
50
+ # Can also be a pattern (./**/*plugin.rb)
51
+ def import_local(path)
52
+ Dir[path].each do |file|
53
+ require File.expand_path(file) # without the expand_path it would fail if the path doesn't start with ./
54
+ refresh_plugins
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,87 @@
1
+ require 'danger/danger_core/violation'
2
+ require 'danger/plugin_support/plugin'
3
+
4
+ module Danger
5
+ class DangerfileMessagingPlugin < Plugin
6
+ def initialize(dangerfile)
7
+ super(dangerfile)
8
+
9
+ @warnings = []
10
+ @errors = []
11
+ @messages = []
12
+ @markdowns = []
13
+ end
14
+
15
+ # @!group Core
16
+ # Print markdown to below the table
17
+ #
18
+ # @param [String] message
19
+ # The markdown based message to be printed below the table
20
+ def markdown(message)
21
+ @markdowns << message
22
+ puts "Printing markdown #{message}"
23
+ end
24
+
25
+ # @!group Core
26
+ # Print out a generate message on the PR
27
+ #
28
+ # @param [String] message The message to present to the user
29
+ # @param [Boolean] sticky
30
+ # Whether the message should be kept after it was fixed,
31
+ # defaults to `true`.
32
+ def message(message, sticky: true)
33
+ @messages << Violation.new(message, sticky)
34
+ puts "Printing message '#{message}'"
35
+ end
36
+
37
+ # @!group Core
38
+ # Specifies a problem, but not critical
39
+ #
40
+ # @param [String] message The message to present to the user
41
+ # @param [Boolean] sticky
42
+ # Whether the message should be kept after it was fixed,
43
+ # defaults to `true`.
44
+ def warn(message, sticky: true)
45
+ return if should_ignore_violation(message)
46
+ @warnings << Violation.new(message, sticky)
47
+ puts "Printing warning '#{message}'"
48
+ end
49
+
50
+ # @!group Core
51
+ # Declares a CI blocking error
52
+ #
53
+ # @param [String] message
54
+ # The message to present to the user
55
+ # @param [Boolean] sticky
56
+ # Whether the message should be kept after it was fixed,
57
+ # defaults to `true`.
58
+ def fail(message, sticky: true)
59
+ return if should_ignore_violation(message)
60
+ @errors << Violation.new(message, sticky)
61
+ puts "Raising error '#{message}'"
62
+ end
63
+
64
+ def status_report
65
+ {
66
+ errors: @errors.map(&:message).clone.freeze,
67
+ warnings: @warnings.map(&:message).clone.freeze,
68
+ messages: @messages.map(&:message).clone.freeze,
69
+ markdowns: @markdowns.clone.freeze
70
+ }
71
+ end
72
+
73
+ def violation_report
74
+ {
75
+ errors: @errors.clone.freeze,
76
+ warnings: @warnings.clone.freeze,
77
+ messages: @messages.clone.freeze
78
+ }
79
+ end
80
+
81
+ private
82
+
83
+ def should_ignore_violation(message)
84
+ env.request_source.ignored_violations.include? message
85
+ end
86
+ end
87
+ end
@@ -2,7 +2,7 @@ require 'claide'
2
2
  require 'claide/informative_error'
3
3
 
4
4
  module Danger
5
- # Ripped direct from CocoaPods-Core - thanks!
5
+ # From below here - this entire file was taken verbatim for CocoaPods-Core.
6
6
 
7
7
  #-------------------------------------------------------------------------#
8
8
 
@@ -0,0 +1,31 @@
1
+ module Danger
2
+ class Plugin
3
+ def initialize(dangerfile)
4
+ @dangerfile = dangerfile
5
+ end
6
+
7
+ def self.instance_name
8
+ self.to_s.gsub("Danger", "").danger_underscore.split('/').last
9
+ end
10
+
11
+ # Both of these methods exist on all objects
12
+ # http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-warn
13
+ # http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-fail
14
+ # However, as we're using using them in the DSL, they won't
15
+ # get method_missing called correctly.
16
+
17
+ def warn(*args, &blk)
18
+ method_missing(:warn, *args, &blk)
19
+ end
20
+
21
+ def fail(*args, &blk)
22
+ method_missing(:fail, *args, &blk)
23
+ end
24
+
25
+ # Since we have a reference to the Dangerfile containing all the information
26
+ # We need to redirect the self calls to the Dangerfile
27
+ def method_missing(method_sym, *arguments, &_block)
28
+ @dangerfile.send(method_sym, *arguments)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,70 @@
1
+ require 'yard'
2
+
3
+ module Danger
4
+ class PluginParser
5
+ attr_accessor :registry
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def parse
12
+ # could this go in a singleton-y place instead?
13
+ # like class initialize?
14
+ YARD::Tags::Library.define_tag('tags', :tags)
15
+
16
+ files = ["lib/danger/plugin_support/plugin.rb", @path]
17
+ self.registry = YARD::Registry.load(files, true)
18
+ end
19
+
20
+ def classes_in_file
21
+ self.registry.all
22
+ .select { |thing| thing.type == :class }
23
+ .select { |klass| klass.file == @path }
24
+ end
25
+
26
+ def plugins_from_classes(classes)
27
+ classes.select { |klass| klass.inheritance_tree.map(&:name).include? :Plugin }
28
+ end
29
+
30
+ def to_dict(classes)
31
+ d_meth = lambda do |meth|
32
+ return nil if meth.nil?
33
+ {
34
+ name: meth.name,
35
+ body_md: meth.docstring,
36
+ tags: meth.tags.map do |t|
37
+ {
38
+ name: t.tag_name,
39
+ types: t.types
40
+ }
41
+ end
42
+ }
43
+ end
44
+
45
+ d_attr = lambda do |attribute|
46
+ {
47
+ read: d_meth.call(attribute[:read]),
48
+ write: d_meth.call(attribute[:write])
49
+ }
50
+ end
51
+
52
+ classes.map do |klass|
53
+ {
54
+ name: klass.name.to_s,
55
+ body_md: klass.docstring,
56
+ example_code: klass.tags.select { |t| t.tag_name == "example" }.map(&:text).compact,
57
+ attributes: klass.attributes[:instance].map do |pair|
58
+ {
59
+ pair.first => d_attr.call(pair.last)
60
+ }
61
+ end,
62
+ methods: (klass.meths - klass.inherited_meths).select { |m| m.visibility == :public }.map { |m| d_meth.call(m) },
63
+ tags: klass.tags.select { |t| t.tag_name == "tags" }.map(&:name).compact,
64
+ see: klass.tags.select { |t| t.tag_name == "see" }.map(&:name).map(&:split).flatten.compact,
65
+ file: klass.file
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end