ducalis 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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