closing_comments 0.1

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 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: []