closing_comments 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3db284866963dbbf428bd2ba95eea36824e3b1b2
4
+ data.tar.gz: d194e7ac4fc744fda9df95cfb01ae078f8a187db
5
+ SHA512:
6
+ metadata.gz: 3237f635443fdcd056454dd5295f1eeab4a1c54650050136657e7f0b02d51697919f32302c4d0ec629d01c73bf8fc7aece58729a13ca8938e9c5e797661a317c
7
+ data.tar.gz: 8a95866c78a9114ed6e69354e8400ce9b0ee28a7b2c321cfab30ffdbbb02a38cd6c58da3a4586bf4e5666411af01f5159f8f6dca2428cf1209e1dc2e4b54a026
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
5
+ require 'optparse'
6
+ require 'closing_comments'
7
+
8
+ module ClosingComments
9
+ class App
10
+ def initialize(args)
11
+ @args = args
12
+ @processor = options.fetch(:processor, Processor::Reporter.new)
13
+ end
14
+
15
+ def run
16
+ @args = ['.'] if @args.empty?
17
+ @args.each(&method(:process))
18
+ @processor.report
19
+ Kernel.exit(@processor.success? ? 0 : 1)
20
+ end
21
+
22
+ def process(path)
23
+ return @processor.process(path: path) if File.file?(path)
24
+ Dir.glob(File.join(path, '/**/*.rb')).each(&method(:process))
25
+ end
26
+
27
+ def options
28
+ {}.tap do |options|
29
+ OptionParser.new do |opts|
30
+ opts.banner = 'Usage: closing_comments [options]'
31
+ opts.on('-a', '--auto-fix', 'Automatically fix issues') do
32
+ options[:processor] = Processor::Fixer.new
33
+ end
34
+ end.parse!(@args)
35
+ end
36
+ end
37
+ end # class App
38
+ end # module ClosingComments
39
+
40
+ ClosingComments::App.new(ARGV).run
data/ensure_version.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'singleton'
4
+
5
+ require 'colorize'
6
+
7
+ class EnsureVersion
8
+ include Singleton
9
+
10
+ def call
11
+ if compare
12
+ puts "#{'[OK]'.green} Local version (#{local_version}) higher than " \
13
+ "remote (#{remote_version})."
14
+ Kernel.exit(0)
15
+ end
16
+ puts "#{'[FAIL]'.red} Local version (#{local_version}) not higher than " \
17
+ "remote (#{remote_version}), please bump."
18
+ Kernel.exit(1)
19
+ end
20
+
21
+ private
22
+
23
+ def compare
24
+ local_version > remote_version
25
+ end
26
+
27
+ def local_version
28
+ Gem.loaded_specs[name].version
29
+ end
30
+
31
+ def remote_version
32
+ @remote_version ||= begin
33
+ version = JSON.parse(rubygems_response.body).fetch('version')
34
+ Gem::Version.new(version == 'unknown' ? 0 : version)
35
+ end
36
+ end
37
+
38
+ # This method reeks of :reek:FeatureEnvy (url).
39
+ def rubygems_response
40
+ url = URI.parse("https://rubygems.org/api/v1/versions/#{name}/latest.json")
41
+ req = Net::HTTP::Get.new(url.to_s)
42
+ Net::HTTP.start(url.host, url.port, use_ssl: true) do |http|
43
+ http.request(req)
44
+ end
45
+ end
46
+
47
+ def name
48
+ 'closing_comments'
49
+ end
50
+ end # class EnsureVersion
51
+
52
+ EnsureVersion.instance.call if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,3 @@
1
+ require 'closing_comments/commentable'
2
+ require 'closing_comments/processor'
3
+ require 'closing_comments/source'
@@ -0,0 +1,97 @@
1
+ require 'forwardable'
2
+ require 'singleton'
3
+
4
+ module ClosingComments
5
+ class Commentable
6
+ extend Forwardable
7
+
8
+ def_delegators :@node, :children, :loc
9
+ def_delegators :loc, :first_line, :last_line
10
+
11
+ def initialize(node)
12
+ @node = node
13
+ end
14
+
15
+ def name
16
+ @name ||= name!(children.first)
17
+ end
18
+
19
+ def single_line?
20
+ first_line == last_line
21
+ end
22
+
23
+ def ending
24
+ "end # #{entity} #{name}"
25
+ end
26
+
27
+ private
28
+
29
+ # This method reeks of :reek:FeatureEnvy and :reek:TooManyStatements.
30
+ def name!(node)
31
+ return node unless node.is_a?(Parser::AST::Node)
32
+ type = node.type
33
+ return '' if type == :cbase
34
+ first, second = node.children
35
+ if type == :str
36
+ loc = node.loc
37
+ # Preserve quote formatting, some folks may prefer double quotes.
38
+ return "#{loc.begin.source}#{first}#{loc.end.source}"
39
+ end
40
+ first ? [name!(first), second].join('::') : second
41
+ end
42
+
43
+ class Class < Commentable
44
+ def entity
45
+ 'class'
46
+ end
47
+ end # class Class
48
+
49
+ class Module < Commentable
50
+ def entity
51
+ 'module'
52
+ end
53
+ end # class Module
54
+
55
+ class Block < Commentable
56
+ RSPEC = %i[context describe shared_context shared_examples].freeze
57
+ private_constant :RSPEC
58
+
59
+ def commentable?
60
+ dispatch.type == :send && rspec?
61
+ end
62
+
63
+ def name
64
+ name!(dispatch.children[2])
65
+ end
66
+
67
+ def entity
68
+ return message if implicit_receiver?
69
+ [name!(receiver), message].join('.')
70
+ end
71
+
72
+ private
73
+
74
+ def rspec?
75
+ return false unless implicit_receiver? || name!(receiver) == :RSpec
76
+ RSPEC.include?(message)
77
+ end
78
+
79
+ def dispatch
80
+ children.first
81
+ end
82
+
83
+ def receiver
84
+ dispatch.children.first
85
+ end
86
+
87
+ # This method reeks of :reek:NilCheck.
88
+ def implicit_receiver?
89
+ receiver.nil?
90
+ end
91
+
92
+ def message
93
+ dispatch.children[1]
94
+ end
95
+ end # class Block
96
+ end # class Commentable
97
+ end # module ClosingComments
@@ -0,0 +1,70 @@
1
+ require 'colorize'
2
+
3
+ module ClosingComments
4
+ class Processor
5
+ def initialize
6
+ @reportables = {}
7
+ end
8
+
9
+ # TODO(marcinw): catch parsing errors;
10
+ def process(path:)
11
+ source = Source.from(path: path)
12
+ handle(source)
13
+ @reportables[path] = source if source.problematic?
14
+ end
15
+
16
+ def success?
17
+ @reportables.empty?
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :reportables
23
+
24
+ class Reporter < Processor
25
+ def handle(source)
26
+ print source.problematic? ? 'F'.red : '.'.green
27
+ end
28
+
29
+ def report
30
+ puts("\n\n")
31
+ return puts 'All good!'.green if reportables.empty?
32
+ puts "Problems #{action} in #{reportables.count} files:\n".red
33
+ reportables.each(&method(:report_file))
34
+ end
35
+
36
+ private
37
+
38
+ def action
39
+ 'found'
40
+ end
41
+
42
+ def report_file(path, source)
43
+ puts "#{path}:"
44
+ source.problems.sort_by(&:line).each do |problem|
45
+ puts " #{problem.line}:#{problem.column} #{problem.message}"
46
+ end
47
+ puts('')
48
+ end
49
+ end # class Reporter
50
+
51
+ class Fixer < Reporter
52
+ # This method reeks of :reek:FeatureEnvy.
53
+ def handle(source)
54
+ return super unless source.problematic?
55
+ File.write(source.path, source.fix)
56
+ print 'A'.yellow
57
+ end
58
+
59
+ private
60
+
61
+ def action
62
+ 'fixed'
63
+ end
64
+ end # class Fixer
65
+
66
+ class JSON < Processor
67
+ def handle(_); end # Don't stare, I'm just implementing an API, ok?
68
+ end # class JSON
69
+ end # class Processor
70
+ end # module ClosingComments
@@ -0,0 +1,133 @@
1
+ require 'forwardable'
2
+ require 'parser/ruby24'
3
+
4
+ module ClosingComments
5
+ class Source
6
+ extend Forwardable
7
+
8
+ attr_reader :path, :content
9
+ def_delegators :visitor, :entities
10
+
11
+ def self.from(path:)
12
+ new(path: path, content: File.read(path))
13
+ end
14
+
15
+ def initialize(path:, content:)
16
+ @path = path
17
+ @content = content
18
+ end
19
+
20
+ def problematic?
21
+ problems.count.positive?
22
+ end
23
+
24
+ def problems
25
+ @problems ||=
26
+ entities
27
+ .reject(&:single_line?)
28
+ .reject(&method(:commented?))
29
+ .map { |ent| Problem.new(ent, line(number: ent.last_line)) }
30
+ end
31
+
32
+ def fix
33
+ lines.map.with_index(1) { |default, no| fixes[no] || default }.join("\n")
34
+ end
35
+
36
+ private
37
+
38
+ def fixes
39
+ @fixes ||=
40
+ problems
41
+ .map { |problem| [problem.line, problem.fix] }
42
+ .to_h
43
+ end
44
+
45
+ def line(number:)
46
+ lines[number - 1]
47
+ end
48
+
49
+ def lines
50
+ @lines ||= content.split("\n").push('')
51
+ end
52
+
53
+ # This method reeks of :reek:FeatureEnvy (entity).
54
+ def commented?(entity)
55
+ line(number: entity.last_line).end_with?(entity.ending)
56
+ end
57
+
58
+ def visitor
59
+ @visitor ||= visitor_class.new(content)
60
+ end
61
+
62
+ def visitor_class
63
+ path.end_with?('_spec.rb') ? Visitor::Spec : Visitor
64
+ end
65
+
66
+ class Problem
67
+ def initialize(entity, line)
68
+ @entity = entity
69
+ @line = line
70
+ end
71
+
72
+ def line
73
+ @entity.last_line
74
+ end
75
+
76
+ def column
77
+ @line.index('end')
78
+ end
79
+
80
+ def fix
81
+ @line[0...column] + @entity.ending
82
+ end
83
+
84
+ def message
85
+ "missing #{@entity.entity} closing comment"
86
+ end
87
+
88
+ def to_h
89
+ {
90
+ line: line,
91
+ column: column,
92
+ message: message,
93
+ }
94
+ end
95
+ end # class Problem
96
+
97
+ class Visitor
98
+ attr_reader :entities
99
+
100
+ def initialize(content)
101
+ @entities = []
102
+ recursively_visit(Parser::Ruby24.parse(content))
103
+ entities.freeze
104
+ end
105
+
106
+ private
107
+
108
+ def recursively_visit(node)
109
+ return unless node.is_a?(Parser::AST::Node)
110
+ visit_current(node)
111
+ node.children.each(&method(:recursively_visit))
112
+ end
113
+
114
+ def visit_current(node)
115
+ case node.type
116
+ when :class then entities << Commentable::Class.new(node)
117
+ when :module then entities << Commentable::Module.new(node)
118
+ end
119
+ end
120
+
121
+ class Spec < Visitor
122
+ private
123
+
124
+ def visit_current(node)
125
+ return super unless node.type == :block
126
+ block = Commentable::Block.new(node)
127
+ entities << block if block.commentable?
128
+ end
129
+ end # class Spec
130
+ end # class Visitor
131
+ private_constant :Visitor
132
+ end # class Source
133
+ end # module ClosingComments
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: closing_comments
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Marcin Wyszynski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.14'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 1.14.6
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '1.14'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.14.6
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '12.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '12.0'
75
+ description: Helper to add closing comments to Ruby entities
76
+ email:
77
+ executables:
78
+ - closing_comments
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - bin/closing_comments
83
+ - ensure_version.rb
84
+ - lib/closing_comments.rb
85
+ - lib/closing_comments/commentable.rb
86
+ - lib/closing_comments/processor.rb
87
+ - lib/closing_comments/source.rb
88
+ homepage: https://github.com/marcinwyszynski/closing_comments
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.6.12
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Helper to add closing comments to Ruby entities
112
+ test_files: []