ducalis 0.8.0 → 0.9.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
  SHA256:
3
- metadata.gz: 9b9c876d0a00b147a1b951611b7ad2f0e7c1acfaecdb03f00a76a206e582678b
4
- data.tar.gz: 447e6dbfb3ae6d84b6f63234ae66aa7db0741ef463daaca07ed3715bcccdf160
3
+ metadata.gz: 4558fbf8708c836a3ed1b13c07831f92f02f60a3e428996df38002ba68540440
4
+ data.tar.gz: b8423308b7ad62219226f0db48bebbcd9bde088f7ba3a938e93c078e914c08ee
5
5
  SHA512:
6
- metadata.gz: cc5cfd83af5d1754ece8f915ffcf04850f39f167fa79c89845b610c9e1b53835a536479598241d1e81e7559f4265ee04d665073f7190e734acea05c270667d74
7
- data.tar.gz: 36ee864dfdd14acc6f941ca5ade14876cff2cfd2a49020d146326010340e9ee7f519c5e36c740f6e46b5323c04a64ab3aeea4fe20277594fa6b5c72c236fff1b
6
+ metadata.gz: c91904bc9d0a53b0d626e642d4cc86b0436457e20e4d623c530116869dcd34c6df8e41f9447ab3688f3e2c0b4f7866ca300c83f91e5a67587379f1e7676cb14d
7
+ data.tar.gz: 45307edbf9c217bf7f757b59f3d98e5fec1902a60bb8079c043a4a846d3d653ce301a8de146cd911b74603c80c08a64db524292046b5f0dfdf0fc66567fc3148
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ducalis (0.8.0)
4
+ ducalis (0.9.0)
5
5
  git (~> 1.3, >= 1.3.0)
6
- policial (= 0.0.4)
6
+ octokit (>= 4.1.0)
7
7
  regexp-examples (~> 1.3, >= 1.3.2)
8
8
  rubocop (>= 0.45.0)
9
9
 
@@ -14,37 +14,17 @@ GEM
14
14
  public_suffix (>= 2.0.2, < 4.0)
15
15
  ast (2.4.0)
16
16
  coderay (1.1.2)
17
- coffee-script (2.4.1)
18
- coffee-script-source
19
- execjs
20
- coffee-script-source (1.12.2)
21
- coffeelint (1.16.1)
22
- coffee-script
23
- execjs
24
- json
25
17
  diff-lcs (1.3)
26
- eslintrb (2.1.0)
27
- execjs
28
- multi_json (>= 1.3)
29
- rake
30
- execjs (2.7.0)
31
- faraday (0.14.0)
18
+ faraday (0.15.0)
32
19
  multipart-post (>= 1.2, < 3)
33
20
  git (1.3.0)
34
- json (2.1.0)
35
21
  method_source (0.9.0)
36
- multi_json (1.13.1)
37
22
  multipart-post (2.0.0)
38
- octokit (4.8.0)
23
+ octokit (4.6.0)
39
24
  sawyer (~> 0.8.0, >= 0.5.3)
40
25
  parallel (1.12.1)
41
26
  parser (2.5.0.0)
42
27
  ast (~> 2.4.0)
43
- policial (0.0.4)
44
- coffeelint (~> 1.14)
45
- eslintrb (~> 2.0)
46
- octokit (~> 4.3)
47
- rubocop (~> 0.39)
48
28
  powerpack (0.1.1)
49
29
  pry (0.11.3)
50
30
  coderay (~> 1.1.0)
@@ -77,7 +57,7 @@ GEM
77
57
  sawyer (0.8.1)
78
58
  addressable (>= 2.3.5, < 2.6)
79
59
  faraday (~> 0.8, < 1.0)
80
- unicode-display_width (1.3.0)
60
+ unicode-display_width (1.3.2)
81
61
 
82
62
  PLATFORMS
83
63
  ruby
data/README.md CHANGED
@@ -20,37 +20,32 @@ Add this line to your application's `Gemfile`:
20
20
  gem 'ducalis'
21
21
  ```
22
22
 
23
- There are a lot of variants how you can use __Ducalis__:
23
+ __Ducalis__ is CLI application. By defaukt it will notify you about any possible
24
+ violations in CLI.
24
25
 
25
- 1. As CLI application. In this mode __Ducalis__ will notify you about any
26
- possible violations in CLI.
27
26
  ```
28
27
  ducalis
29
28
  ducalis app/controllers/
30
29
  ```
31
- As __Ducalis__ allows to pass build even with violations it's make sense to run
30
+
31
+ __Ducalis__ allows to pass build even with violations it's make sense to run
32
32
  __Ducalis__ across current branch or index:
33
+
33
34
  ```
34
35
  ducalis --branch
35
36
  ducalis --index
36
37
  ```
37
38
 
38
- 2. As CLI application in CI mode: In this mode __Ducalis__ will notify you about
39
- any violations in your PR.
39
+ Additionally you can pass `--reporter` argument to notify about found violations
40
+ in boundaries of PR:
41
+
40
42
  ```
41
- ducalis --ci --repo="author/repo" --id=3575 --dry
42
- ducalis --ci --repo="author/repo" --id=3575
43
- ducalis --ci --adapter=circle # mode for running on CircleCI
43
+ ducalis --reporter "author/repo#42"
44
+ ducalis --reporter "circleci"
44
45
  ```
45
- `--dry` option declares that output will be printed in console, if you will run
46
- without this option __Ducalis__ will notify about violations in your PR.
47
- _N.B._ You should provide GITHUB_TOKEN Env to allow __Ducalis__ download your PR
48
- code and write review comments.
49
-
50
- 3. As stand-alone server mode: In this mode __Ducalis__ will work as server,
51
- listen webhooks from GitHub, and notify about any violations in PR. There is a
52
- `Dockerfile` which you could use to run server for this or run it manually like
53
- rack application. All related files are located in the `client/` directory.
46
+
47
+ _N.B._ You should provide `GITHUB_TOKEN` Env to allow __Ducalis__ download your
48
+ PR code and write review comments.
54
49
 
55
50
  In CLI modes you can provide yours `.ducalis.yml` file based on
56
51
  [default](https://github.com/ignat-z/ducalis/blob/master/config/.ducalis.yml) by
@@ -6,38 +6,38 @@ $LOAD_PATH.unshift("#{__dir__}/../lib")
6
6
  require 'ducalis'
7
7
  require 'benchmark'
8
8
 
9
- if Ducalis::PassedArgs.help_command?
9
+ cli_arguments = Ducalis::CliArguments.new
10
+
11
+ if cli_arguments.help_command?
10
12
  puts 'You can start Ducalis in the next modes:'
11
- puts " --ci check files from remote PR. \
12
- Env `GITHUB_TOKEN` should be available to receive PRs files."
13
13
  puts ' --branch check files in RuboCop style against branch.'
14
14
  puts ' --index check files in RuboCop style against index.'
15
15
  puts ' --all [default] check all files in RuboCop style.'
16
- puts ''
16
+ puts
17
+ puts 'Use --reporter flag to pass how to report violations'
18
+ puts ' Ex: ducalis --reporter "user/repo#42"'
19
+ puts ' ducalis --reporter "circleci"'
17
20
 
18
- Ducalis::CLI.new(ARGV).start
19
21
  exit 0
20
22
  end
21
23
 
22
- if ARGV.any? { |arg| arg == '--docs' }
24
+ if cli_arguments.docs_command?
23
25
  require 'ducalis/documentation'
26
+
24
27
  File.write(ARGV[1] || 'DOCUMENTATION.md', Documentation.new.call)
28
+
25
29
  exit 0
26
30
  end
27
31
 
28
- if Ducalis::PassedArgs.ci_mode?
29
- Ducalis::CLI.new(ARGV - %w[--ci]).start
30
- else
31
- Ducalis::PassedArgs.process_args!
32
+ cli_arguments.process!
32
33
 
33
- result = 0
34
- cli = RuboCop::CLI.new
35
- time = Benchmark.realtime do
36
- result = cli.run
37
- end
38
-
39
- puts "Finished in #{time} seconds" if cli.options[:debug]
40
- puts "\033[42mBuild still green. Nothing to worry about\033[0m" if result == 1
34
+ result = 0
35
+ cli = RuboCop::CLI.new
36
+ time = Benchmark.realtime do
37
+ result = cli.run
41
38
  end
42
39
 
40
+ puts "Finished in #{time} seconds" if cli.options[:debug]
41
+ puts "\033[32mBuild still green. Nothing to worry about\033[0m" if result == 1
42
+
43
43
  exit 0
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
30
30
 
31
31
  spec.add_dependency 'git', '~> 1.3', '>= 1.3.0'
32
- spec.add_dependency 'policial', '0.0.4'
32
+ spec.add_dependency 'octokit', '>= 4.1.0'
33
33
  spec.add_dependency 'regexp-examples', '~> 1.3', '>= 1.3.2'
34
34
  spec.add_dependency 'rubocop', '>= 0.45.0'
35
35
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parser/current'
4
- require 'policial'
3
+ require 'rubocop'
5
4
 
6
5
  module Ducalis
7
6
  DOTFILE = '.ducalis.yml'.freeze
@@ -10,41 +9,41 @@ module Ducalis
10
9
  end
11
10
 
12
11
  require 'ducalis/version'
12
+ require 'ducalis/errors'
13
13
 
14
- require 'ducalis/adapters/base'
15
14
  require 'ducalis/adapters/circle_ci'
16
- require 'ducalis/adapters/custom'
17
- require 'ducalis/adapters/pull_request'
15
+ require 'ducalis/adapters/default'
18
16
 
19
- require 'ducalis/commentators/console'
20
- require 'ducalis/commentators/github'
21
-
22
- require 'ducalis/cli'
23
- require 'ducalis/passed_args'
24
- require 'ducalis/runner'
25
- require 'ducalis/utils'
26
-
27
- require 'ducalis/patched_rubocop/diffs'
28
17
  require 'ducalis/patched_rubocop/ducalis_config_loader'
29
- require 'ducalis/patched_rubocop/git_files_access'
30
18
  require 'ducalis/patched_rubocop/git_runner'
31
19
  require 'ducalis/patched_rubocop/git_turget_finder'
32
- require 'ducalis/patched_rubocop/rubo_cop'
20
+ require 'ducalis/patched_rubocop/inject'
21
+ require 'ducalis/patched_rubocop/cop_cast'
22
+
23
+ require 'ducalis/commentators/github'
24
+ require 'ducalis/github_formatter'
25
+
26
+ require 'ducalis/utils'
27
+ require 'ducalis/diffs'
28
+ require 'ducalis/rubo_cop'
29
+ require 'ducalis/cli_arguments'
30
+ require 'ducalis/patch'
31
+ require 'ducalis/git_access'
33
32
 
34
- require 'ducalis/cops/extensions/type_resolving'
35
33
  require 'ducalis/cops/black_list_suffix'
36
34
  require 'ducalis/cops/callbacks_activerecord'
37
35
  require 'ducalis/cops/case_mapping'
38
36
  require 'ducalis/cops/controllers_except'
39
37
  require 'ducalis/cops/data_access_objects'
40
38
  require 'ducalis/cops/descriptive_block_names'
39
+ require 'ducalis/cops/enforce_namespace'
40
+ require 'ducalis/cops/evlis_overusing'
41
+ require 'ducalis/cops/extensions/type_resolving'
41
42
  require 'ducalis/cops/fetch_expression'
42
- require 'ducalis/cops/only_defs'
43
43
  require 'ducalis/cops/keyword_defaults'
44
44
  require 'ducalis/cops/module_like_class'
45
45
  require 'ducalis/cops/multiple_times'
46
- require 'ducalis/cops/evlis_overusing'
47
- require 'ducalis/cops/enforce_namespace'
46
+ require 'ducalis/cops/only_defs'
48
47
  require 'ducalis/cops/options_argument'
49
48
  require 'ducalis/cops/params_passing'
50
49
  require 'ducalis/cops/possible_tap'
@@ -62,7 +61,3 @@ require 'ducalis/cops/too_long_workers'
62
61
  require 'ducalis/cops/uncommented_gem'
63
62
  require 'ducalis/cops/unlocked_gem'
64
63
  require 'ducalis/cops/useless_only'
65
-
66
- require 'ducalis/cops/extensions/rubocop_cast'
67
-
68
- RuboCop::Cop::Cop.prepend(RubocopCast)
@@ -1,22 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Ducalis
4
- module Adapters
5
- class CircleCi < Base
6
- def repo
7
- @repo ||= ENV.fetch('CIRCLE_REPOSITORY_URL')
8
- .sub('https://github.com/', '')
9
- .sub('git@github.com:', '')
10
- .sub('.git', '')
11
- end
3
+ module Adapters
4
+ class CircleCI
5
+ CODE = 'circleci'.freeze
12
6
 
13
- def id
14
- @id ||= ENV.fetch('CI_PULL_REQUEST').split('/').last
15
- end
7
+ def self.suitable_for?(value)
8
+ value == CODE
9
+ end
10
+
11
+ def initialize(_value); end
12
+
13
+ def call
14
+ [repo, id]
15
+ end
16
+
17
+ private
18
+
19
+ def repo
20
+ @repo ||= ENV.fetch('CIRCLE_REPOSITORY_URL')
21
+ .sub('https://github.com/', '')
22
+ .sub('git@github.com:', '')
23
+ .sub('.git', '')
24
+ end
16
25
 
17
- def sha
18
- @sha ||= ENV.fetch('CIRCLE_SHA1')
19
- end
26
+ def id
27
+ @id ||= ENV.fetch('CI_PULL_REQUEST')
28
+ .split('/')
29
+ .last
20
30
  end
21
31
  end
22
32
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Adapters
4
+ class Default
5
+ def self.suitable_for?(_value)
6
+ true
7
+ end
8
+
9
+ def initialize(value)
10
+ @value = value
11
+ end
12
+
13
+ def call
14
+ @value.split('#')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ducalis
4
+ class CliArguments
5
+ ADAPTERS = [
6
+ Adapters::CircleCI,
7
+ Adapters::Default
8
+ ].freeze
9
+
10
+ HELP_FLAGS = %w[-h -? --help].freeze
11
+ FORMATTER = %w[--format GithubFormatter].freeze
12
+ DOCS_ARG = :docs
13
+ REPORTER_ARG = :reporter
14
+
15
+ def docs_command?
16
+ ARGV.any? { |arg| arg == to_key(DOCS_ARG) }
17
+ end
18
+
19
+ def help_command?
20
+ ARGV.any? { |arg| HELP_FLAGS.include?(arg) }
21
+ end
22
+
23
+ def process!
24
+ detect_git_mode!
25
+ detect_reporter!
26
+ end
27
+
28
+ private
29
+
30
+ def detect_reporter!
31
+ reporter_index = ARGV.index(to_key(REPORTER_ARG)) || return
32
+ reporter = ARGV[reporter_index + 1]
33
+ [to_key(REPORTER_ARG), reporter].each { |arg| ARGV.delete(arg) }
34
+ ARGV.push(*FORMATTER)
35
+ GitAccess.instance.store_pull_request!(find_pull_request(reporter))
36
+ end
37
+
38
+ def detect_git_mode!
39
+ git_mode = GitAccess::MODES.keys.find do |mode|
40
+ ARGV.include?(to_key(mode))
41
+ end
42
+ return unless git_mode
43
+ ARGV.delete(to_key(git_mode))
44
+ GitAccess.instance.flag = git_mode
45
+ end
46
+
47
+ def find_pull_request(value)
48
+ ADAPTERS.find { |adapter| adapter.suitable_for?(value) }.new(value).call
49
+ end
50
+
51
+ def to_key(key)
52
+ "--#{key}"
53
+ end
54
+ end
55
+ end
@@ -6,29 +6,31 @@ module Ducalis
6
6
  STATUS = 'COMMENT'.freeze
7
7
  SIMILARITY_THRESHOLD = 0.8
8
8
 
9
- def initialize(config)
10
- @config = config
9
+ def initialize(repo, id)
10
+ @repo = repo
11
+ @id = id
11
12
  end
12
13
 
13
- def call(violations)
14
- comments = violations.map do |violation|
15
- next if commented?(violation)
16
- generate_comment(violation)
17
- end.compact
14
+ def call(offenses)
15
+ comments = offenses.reject { |offense| already_commented?(offense) }
16
+ .map { |offense| present_offense(offense) }
17
+
18
18
  return if comments.empty?
19
+
19
20
  Utils.octokit
20
- .create_pull_request_review(@config.repo, @config.id,
21
+ .create_pull_request_review(@repo, @id,
21
22
  event: STATUS, comments: comments)
22
23
  end
23
24
 
24
25
  private
25
26
 
26
- def commented?(violation)
27
- commented_violations.find do |commented_violation|
27
+ def already_commented?(offense)
28
+ current_offence = present_offense(offense)
29
+ commented_offenses.find do |commented_offense|
28
30
  [
29
- violation.filename == commented_violation[:path],
30
- violation.line.patch_position == commented_violation[:position],
31
- similar_messages?(violation.message, commented_violation[:body])
31
+ current_offence[:path] == commented_offense[:path],
32
+ current_offence[:position] == commented_offense[:position],
33
+ similar_messages?(current_offence[:body], commented_offense[:body])
32
34
  ].all?
33
35
  end
34
36
  end
@@ -38,18 +40,21 @@ module Ducalis
38
40
  Utils.similarity(message, body) > SIMILARITY_THRESHOLD
39
41
  end
40
42
 
41
- def commented_violations
42
- @commented_violations ||=
43
- Utils.octokit.pull_request_comments(@config.repo, @config.id)
44
- end
45
-
46
- def generate_comment(violation)
43
+ def present_offense(offense)
47
44
  {
48
- body: violation.message,
49
- path: violation.filename,
50
- position: violation.line.patch_position
45
+ body: offense.message,
46
+ path: diff_for(offense).path,
47
+ position: diff_for(offense).patch_line(offense.line)
51
48
  }
52
49
  end
50
+
51
+ def diff_for(offense)
52
+ GitAccess.instance.for(offense.location.source_buffer.name)
53
+ end
54
+
55
+ def commented_offenses
56
+ @_commented_offenses ||= Utils.octokit.pull_request_comments(@repo, @id)
57
+ end
53
58
  end
54
59
  end
55
60
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diffs
4
+ class BaseDiff
5
+ attr_reader :diff, :path
6
+
7
+ def initialize(diff, path)
8
+ @diff = diff
9
+ @path = path
10
+ end
11
+ end
12
+
13
+ class NilDiff < BaseDiff
14
+ def changed?(*)
15
+ true
16
+ end
17
+
18
+ def patch_line(*)
19
+ -1
20
+ end
21
+ end
22
+
23
+ class GitDiff < BaseDiff
24
+ def changed?(changed_line)
25
+ patch.line_for(changed_line).changed?
26
+ end
27
+
28
+ def patch_line(changed_line)
29
+ patch.line_for(changed_line).patch_position
30
+ end
31
+
32
+ private
33
+
34
+ def patch
35
+ Ducalis::Patch.new(diff.patch)
36
+ end
37
+ end
38
+
39
+ private_constant :BaseDiff, :NilDiff, :GitDiff
40
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ducalis
4
+ class MissingGit < ::StandardError
5
+ MESSAGE = "Can't find .git folder.".freeze
6
+
7
+ def initialize(msg = MESSAGE)
8
+ super
9
+ end
10
+ end
11
+
12
+ class MissingToken < ::StandardError
13
+ MESSAGE = 'You should provide token in order to interact with GitHub'.freeze
14
+
15
+ def initialize(msg = MESSAGE)
16
+ super
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git'
4
+ require 'singleton'
5
+
6
+ class GitAccess
7
+ DELETED = 'deleted'.freeze
8
+ GIT_DIR = '.git'.freeze
9
+
10
+ MODES = {
11
+ branch: ->(git) { git.diff('origin/master') },
12
+ index: ->(git) { git.diff('HEAD') }
13
+ }.freeze
14
+
15
+ include Diffs
16
+ include Singleton
17
+
18
+ attr_accessor :flag, :repo, :id
19
+
20
+ def store_pull_request!(repo, id)
21
+ self.repo = repo
22
+ self.id = id
23
+ end
24
+
25
+ def changed_files
26
+ changes.map(&:path)
27
+ end
28
+
29
+ def for(path)
30
+ find(path)
31
+ end
32
+
33
+ private
34
+
35
+ def under_git?
36
+ @_under_git ||= Dir.exist?(File.join(Dir.pwd, GIT_DIR))
37
+ end
38
+
39
+ def changes
40
+ return default_value if flag.nil? || !under_git?
41
+ @_changes ||= patch_diffs
42
+ end
43
+
44
+ def patch_diffs
45
+ MODES.fetch(flag)
46
+ .call(Git.open(Dir.pwd))
47
+ .reject { |diff| diff.type == DELETED }
48
+ .select { |diff| File.exist?(diff.path) }
49
+ .map { |diff| GitDiff.new(diff, diff.path) }
50
+ end
51
+
52
+ def default_value
53
+ raise MissingGit unless flag.nil?
54
+ []
55
+ end
56
+
57
+ def find(path)
58
+ return NilDiff.new(nil, path) if changes.empty?
59
+ changes.find { |diff| diff.path == path }
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class GithubFormatter < RuboCop::Formatter::BaseFormatter
4
+ def started(_target_files)
5
+ @all = []
6
+ end
7
+
8
+ def file_finished(_file, offenses)
9
+ print '.'
10
+ @all << offenses unless offenses.empty?
11
+ end
12
+
13
+ def finished(_inspected_files)
14
+ print "\n"
15
+ Ducalis::Commentators::Github.new(
16
+ GitAccess.instance.repo,
17
+ GitAccess.instance.id
18
+ ).call(@all.flatten)
19
+ end
20
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ducalis
4
+ class Patch
5
+ RANGE_LINE = /^@@ .+\+(?<line_number>\d+),/
6
+ MODIFIED_LINE = /^\+(?!\+|\+)/
7
+ NOT_REMOVED_LINE = /^[^-]/
8
+ ANY_LINE = /.*/
9
+
10
+ DIFF_LINES = {
11
+ RANGE_LINE => lambda do |lines, _line_number, line, _position|
12
+ [lines, line.match(RANGE_LINE)[:line_number].to_i]
13
+ end,
14
+ MODIFIED_LINE => lambda do |lines, line_number, line, position|
15
+ [lines + [Line.new(line_number, line, position)], line_number + 1]
16
+ end,
17
+ NOT_REMOVED_LINE => lambda do |lines, line_number, _line, _position|
18
+ [lines, line_number + 1]
19
+ end,
20
+ ANY_LINE => lambda do |lines, line_number, _line, _position|
21
+ [lines, line_number]
22
+ end
23
+ }.freeze
24
+
25
+ def initialize(patch)
26
+ diff_only = patch[patch.match(RANGE_LINE).begin(0)..-1]
27
+ @patch_lines = diff_only.lines.to_enum.with_index
28
+ end
29
+
30
+ def line_for(line_number)
31
+ changed_lines.detect do |line|
32
+ line.number == line_number
33
+ end || UnchangedLine.new
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :patch_lines
39
+
40
+ def changed_lines
41
+ patch_lines.inject([[], 0]) do |(lines, line_number), (line, position)|
42
+ _regex, action = DIFF_LINES.find { |regex, _action| line =~ regex }
43
+ action.call(lines, line_number, line, position)
44
+ end.first
45
+ end
46
+ end
47
+
48
+ class UnchangedLine
49
+ def initialize(*); end
50
+
51
+ def patch_position
52
+ -1
53
+ end
54
+
55
+ def changed?
56
+ false
57
+ end
58
+ end
59
+
60
+ class Line
61
+ attr_reader :number, :content, :patch_position
62
+
63
+ def initialize(number, content, patch_position)
64
+ @number = number
65
+ @content = content
66
+ @patch_position = patch_position
67
+ end
68
+
69
+ def changed?
70
+ true
71
+ end
72
+ end
73
+ private_constant :Line, :UnchangedLine
74
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PatchedRubocop
4
+ module CopCast
5
+ def add_offense(node, loc, message = nil, severity = nil)
6
+ if PatchedRubocop::CURRENT_VERSION > PatchedRubocop::ADAPTED_VERSION
7
+ super(node, location: loc, message: message, severity: severity)
8
+ else
9
+ super(node, loc, message, severity)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -5,7 +5,7 @@ module PatchedRubocop
5
5
  def inspect_file(file)
6
6
  offenses, updated = super
7
7
  offenses = offenses.select do |offense|
8
- GitFilesAccess.instance.changed?(file.path, offense.line)
8
+ GitAccess.instance.for(file.path).changed?(offense.line)
9
9
  end
10
10
 
11
11
  [offenses, updated]
@@ -3,8 +3,8 @@
3
3
  module PatchedRubocop
4
4
  module GitTurgetFinder
5
5
  def find_files(base_dir, flags)
6
- replacement = GitFilesAccess.instance.changes
7
- return replacement.map(&:full_path) unless replacement.empty?
6
+ replacement = GitAccess.instance.changed_files
7
+ return replacement unless replacement.empty?
8
8
  super
9
9
  end
10
10
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PatchedRubocop
4
+ module Inject
5
+ PATH = Ducalis::DEFAULT_FILE.to_s
6
+
7
+ def self.defaults!
8
+ hash = RuboCop::ConfigLoader.send(:load_yaml_configuration, PATH)
9
+ config = RuboCop::Config.new(hash, PATH)
10
+ puts "configuration from #{PATH}" if RuboCop::ConfigLoader.debug?
11
+ config = RuboCop::ConfigLoader.merge_with_default(config, PATH)
12
+ RuboCop::ConfigLoader
13
+ .instance_variable_set(:@default_configuration, config)
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module PatchedRubocop
4
+ CURRENT_VERSION = Gem::Version.new(RuboCop::Version.version)
5
+ ADAPTED_VERSION = Gem::Version.new('0.46.0')
6
+ end
7
+
3
8
  module RuboCop
4
9
  class ConfigLoader
5
10
  ::Ducalis::Utils.silence_warnings { DOTFILE = ::Ducalis::DOTFILE }
@@ -25,16 +30,5 @@ module RuboCop
25
30
  end
26
31
  end
27
32
 
28
- module PatchedRubocop
29
- MODES = {
30
- branch: ->(git) { git.diff('origin/master') },
31
- index: ->(git) { git.diff('HEAD') },
32
- all: ->(_git) { [] }
33
- }.freeze
34
-
35
- module_function
36
-
37
- def configure!(flag)
38
- GitFilesAccess.instance.flag = flag
39
- end
40
- end
33
+ RuboCop::Cop::Cop.prepend(PatchedRubocop::CopCast)
34
+ PatchedRubocop::Inject.defaults!
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'octokit'
4
+
3
5
  module Ducalis
4
6
  module Utils
5
7
  module_function
6
8
 
7
9
  def octokit
8
- @octokit ||= Octokit::Client.new(access_token: ENV.fetch('GITHUB_TOKEN'))
10
+ @octokit ||= begin
11
+ token = ENV.fetch('GITHUB_TOKEN') { raise MissingToken }
12
+ Octokit::Client.new(access_token: token)
13
+ end
9
14
  end
10
15
 
11
16
  def similarity(string1, string2)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ducalis
4
- VERSION = '0.8.0'.freeze
4
+ VERSION = '0.9.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ducalis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ignat Zakrevsky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-05 00:00:00.000000000 Z
11
+ date: 2018-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git
@@ -31,19 +31,19 @@ dependencies:
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.3.0
33
33
  - !ruby/object:Gem::Dependency
34
- name: policial
34
+ name: octokit
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - '='
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 0.0.4
39
+ version: 4.1.0
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - '='
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 0.0.4
46
+ version: 4.1.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: regexp-examples
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -104,12 +104,9 @@ files:
104
104
  - config/.ducalis.yml
105
105
  - ducalis.gemspec
106
106
  - lib/ducalis.rb
107
- - lib/ducalis/adapters/base.rb
108
107
  - lib/ducalis/adapters/circle_ci.rb
109
- - lib/ducalis/adapters/custom.rb
110
- - lib/ducalis/adapters/pull_request.rb
111
- - lib/ducalis/cli.rb
112
- - lib/ducalis/commentators/console.rb
108
+ - lib/ducalis/adapters/default.rb
109
+ - lib/ducalis/cli_arguments.rb
113
110
  - lib/ducalis/commentators/github.rb
114
111
  - lib/ducalis/cops/black_list_suffix.rb
115
112
  - lib/ducalis/cops/callbacks_activerecord.rb
@@ -119,7 +116,6 @@ files:
119
116
  - lib/ducalis/cops/descriptive_block_names.rb
120
117
  - lib/ducalis/cops/enforce_namespace.rb
121
118
  - lib/ducalis/cops/evlis_overusing.rb
122
- - lib/ducalis/cops/extensions/rubocop_cast.rb
123
119
  - lib/ducalis/cops/extensions/type_resolving.rb
124
120
  - lib/ducalis/cops/fetch_expression.rb
125
121
  - lib/ducalis/cops/keyword_defaults.rb
@@ -143,15 +139,18 @@ files:
143
139
  - lib/ducalis/cops/uncommented_gem.rb
144
140
  - lib/ducalis/cops/unlocked_gem.rb
145
141
  - lib/ducalis/cops/useless_only.rb
142
+ - lib/ducalis/diffs.rb
146
143
  - lib/ducalis/documentation.rb
147
- - lib/ducalis/passed_args.rb
148
- - lib/ducalis/patched_rubocop/diffs.rb
144
+ - lib/ducalis/errors.rb
145
+ - lib/ducalis/git_access.rb
146
+ - lib/ducalis/github_formatter.rb
147
+ - lib/ducalis/patch.rb
148
+ - lib/ducalis/patched_rubocop/cop_cast.rb
149
149
  - lib/ducalis/patched_rubocop/ducalis_config_loader.rb
150
- - lib/ducalis/patched_rubocop/git_files_access.rb
151
150
  - lib/ducalis/patched_rubocop/git_runner.rb
152
151
  - lib/ducalis/patched_rubocop/git_turget_finder.rb
153
- - lib/ducalis/patched_rubocop/rubo_cop.rb
154
- - lib/ducalis/runner.rb
152
+ - lib/ducalis/patched_rubocop/inject.rb
153
+ - lib/ducalis/rubo_cop.rb
155
154
  - lib/ducalis/utils.rb
156
155
  - lib/ducalis/version.rb
157
156
  homepage: https://github.com/ignat-z/ducalis
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ducalis
4
- module Adapters
5
- class Base
6
- attr_reader :options
7
-
8
- def initialize(options)
9
- @options = options
10
- end
11
-
12
- def dry?
13
- @dry ||= @options.fetch(:dry, false)
14
- end
15
- end
16
- end
17
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ducalis
4
- module Adapters
5
- class Custom < Base
6
- def repo
7
- @repo ||= options.fetch(:repo)
8
- end
9
-
10
- def id
11
- @id ||= options.fetch(:id)
12
- end
13
-
14
- def sha
15
- @sha ||= options.fetch(:sha) do
16
- Utils.octokit.pull_request(repo, id).head.sha
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ducalis
4
- module Adapters
5
- class PullRequest < Base
6
- def repo
7
- @repo ||= attributes[:repo]
8
- end
9
-
10
- def id
11
- @id ||= attributes[:number]
12
- end
13
-
14
- def sha
15
- @sha ||= attributes[:head_sha]
16
- end
17
-
18
- private
19
-
20
- def attributes
21
- @attributes ||= Policial::PullRequestEvent.new(options)
22
- .pull_request_attributes
23
- end
24
- end
25
- end
26
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
-
5
- module Ducalis
6
- class CLI
7
- ADAPTERS = {
8
- circle: Adapters::CircleCi,
9
- custom: Adapters::Custom
10
- }.freeze
11
- DEFAULT_ADAPTER = ADAPTERS.keys.last
12
-
13
- def initialize(arguments)
14
- @arguments = arguments
15
- @options = {}
16
- @parser = OptionParser.new
17
- configure_parser!
18
- end
19
-
20
- def start
21
- @parser.parse(@arguments)
22
- Runner.new(adapter.new(@options)).call
23
- end
24
-
25
- private
26
-
27
- def adapter
28
- ADAPTERS.fetch(@options.fetch(:adapter, DEFAULT_ADAPTER)) do
29
- raise "Unsupported adapter #{@options[:adapter]}"
30
- end
31
- end
32
-
33
- def configure_parser!
34
- @parser.banner = 'Usage: ducalis --ci --adapter=ADAPTER'
35
- adapter_option_parsing
36
- id_option_parsing
37
- repo_option_parsing
38
- sha_option_parsing
39
- dry_option_parsing
40
- help_command
41
- end
42
-
43
- def help_command
44
- @parser.on_tail('--help', 'Show this message') do
45
- puts @parser
46
- RuboCop::CLI.new.run(@arguments)
47
- end
48
- end
49
-
50
- def adapter_option_parsing
51
- @parser.on(
52
- '--adapter=ADAPTER',
53
- 'Describes how Ducalis will receive PR information. Default: custom'
54
- ) do |adapter|
55
- @options[:adapter] = adapter.to_sym
56
- end
57
- end
58
-
59
- def id_option_parsing
60
- @parser.on(
61
- '--id=N',
62
- 'PR id, ex: 2347'
63
- ) do |id|
64
- @options[:id] = id
65
- end
66
- end
67
-
68
- def repo_option_parsing
69
- @parser.on(
70
- '--repo=REPO',
71
- 'PR repository, ex: author/repo'
72
- ) do |repo|
73
- @options[:repo] = repo
74
- end
75
- end
76
-
77
- def sha_option_parsing
78
- @parser.on(
79
- '--sha=SHA',
80
- 'Starting commit, can be omitted'
81
- ) do |sha|
82
- @options[:sha] = sha
83
- end
84
- end
85
-
86
- def dry_option_parsing
87
- @options[:dry] = false # default
88
- @parser.on(
89
- '--dry',
90
- 'Allows user to run dry mode, default: false'
91
- ) do |_dry|
92
- @options[:dry] = true
93
- end
94
- end
95
- end
96
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
-
5
- module Ducalis
6
- module Commentators
7
- class Console
8
- DOCUMENTATION_PATH = 'https://ducalis-rb.github.io/'.freeze
9
-
10
- def initialize(config)
11
- @config = config
12
- end
13
-
14
- def call(violations)
15
- violations.each do |violation|
16
- logger.info(generate_message(violation))
17
- end
18
- end
19
-
20
- private
21
-
22
- def generate_message(violation)
23
- [
24
- [cyan(violation.filename), violation.line.patch_position].join(':'),
25
- brown(violation.linter),
26
- bold(ancor(violation))
27
- ].join(' ')
28
- end
29
-
30
- def logger
31
- @logger ||= Logger.new(STDOUT).tap do |logger|
32
- logger.formatter = proc do |_severity, _datetime, _progname, msg|
33
- "#{msg}\n"
34
- end
35
- end
36
- end
37
-
38
- def ancor(violation)
39
- [
40
- DOCUMENTATION_PATH,
41
- '#',
42
- violation.linter.downcase.gsub(/[^[:alpha:]]/, '')
43
- ].join
44
- end
45
-
46
- def bold(text)
47
- "\e[1m#{text}\e[22m"
48
- end
49
-
50
- def brown(text)
51
- "\e[33m#{text}\e[0m"
52
- end
53
-
54
- def cyan(text)
55
- "\e[36m#{text}\e[0m"
56
- end
57
- end
58
- end
59
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubocopCast
4
- OLD_RUBOCOP_VERSION = Gem::Version.new('0.46.0')
5
-
6
- def add_offense(node, loc, message = nil, severity = nil)
7
- if Gem::Version.new(RuboCop::Version.version) > OLD_RUBOCOP_VERSION
8
- super(node, location: loc, message: message, severity: severity)
9
- else
10
- super(node, loc, message, severity)
11
- end
12
- end
13
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ducalis
4
- module PassedArgs
5
- module_function
6
-
7
- HELP_FLAGS = ['-h', '-?', '--help'].freeze
8
-
9
- def help_command?
10
- ARGV.any? { |arg| HELP_FLAGS.include?(arg) }
11
- end
12
-
13
- def ci_mode?
14
- ARGV.any? { |arg| arg == '--ci' }
15
- end
16
-
17
- def process_args!
18
- flag = PatchedRubocop::MODES.keys
19
- .map { |key| key if ARGV.delete("--#{key}") }
20
- .find { |possible_flag| !possible_flag.nil? }
21
- PatchedRubocop.configure!(flag || :all)
22
- end
23
- end
24
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PatchedRubocop
4
- module Diffs
5
- class BaseDiff
6
- attr_reader :full_path, :diff
7
-
8
- def initialize(full_path, diff)
9
- @full_path = full_path
10
- @diff = diff
11
- end
12
- end
13
-
14
- class NilDiff < BaseDiff
15
- def changed?(*)
16
- true
17
- end
18
- end
19
-
20
- class GitDiff < BaseDiff
21
- def changed?(changed_line)
22
- (Policial::Patch.new(diff.patch).changed_lines.detect do |line|
23
- line.number == changed_line
24
- end || Policial::UnchangedLine.new).changed?
25
- end
26
- end
27
-
28
- private_constant :BaseDiff, :NilDiff, :GitDiff
29
- end
30
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'git'
4
- require 'singleton'
5
-
6
- module PatchedRubocop
7
- class GitFilesAccess
8
- DELETED = 'deleted'.freeze
9
-
10
- include PatchedRubocop::Diffs
11
- include Singleton
12
-
13
- attr_accessor :flag
14
-
15
- def initialize
16
- @dir = Dir.pwd
17
- @git = Git.open(@dir)
18
- end
19
-
20
- def changes
21
- @changes ||= MODES.fetch(@flag)
22
- .call(@git)
23
- .map { |diff| GitDiff.new(full_path(diff.path), diff) }
24
- .reject { |diff| diff.diff.type == DELETED }
25
- end
26
-
27
- def changed?(path, line)
28
- find(path).changed?(line)
29
- end
30
-
31
- private
32
-
33
- def full_path(path)
34
- [@dir, path].join('/')
35
- end
36
-
37
- def find(path)
38
- return NilDiff.new(full_path(path), nil) if changes.empty?
39
- changes.find { |x| x.full_path == path }
40
- end
41
- end
42
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Policial
4
- class ConfigLoader
5
- def raw(filename)
6
- File.read(filename)
7
- end
8
- end
9
- end
10
-
11
- module Ducalis
12
- class Runner
13
- def initialize(config)
14
- @config = config
15
- configure
16
- end
17
-
18
- def call
19
- detective = Policial::Detective.new(Utils.octokit)
20
- detective.brief(commit_info)
21
- detective.investigate(ruby: { config_file: Ducalis::DEFAULT_FILE })
22
- commentator.new(config).call(detective.violations)
23
- end
24
-
25
- private
26
-
27
- attr_reader :config
28
-
29
- def commit_info
30
- { repo: config.repo, number: config.id, head_sha: config.sha }
31
- end
32
-
33
- def configure
34
- Octokit.auto_paginate = true
35
- # Style guides were changed to linters in `policial` upstream
36
- if Policial.respond_to?(:linters)
37
- Policial.linters = [Policial::Linters::Ruby]
38
- else
39
- Policial.style_guides = [Policial::StyleGuides::Ruby]
40
- end
41
- end
42
-
43
- def commentator
44
- @commentator ||=
45
- config.dry? ? Commentators::Console : Commentators::Github
46
- end
47
- end
48
- end