chusaku 0.3.2 → 0.6.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: f2976318f89e26e31f77c54e1cc338812ac6a107ec033d53c077fd4eb1759be6
4
- data.tar.gz: 8413d9353d6115929985f3d6275396abeb25d7b9df834ccd0d4e19fd834ef574
3
+ metadata.gz: 46b0340d8c2c5cde79a47703b564be4bec987b2fed0e9561ce658227ac3a2076
4
+ data.tar.gz: 8d2384684ad0c72d2f763446177b16d97d76f2c84c95bf4a7b5ff8e08cf32c23
5
5
  SHA512:
6
- metadata.gz: 44c12d0f6c0f970cd3a0e5c76cd14b18d8bfbf16f7d39e8789e4c09c87a8273a8b3e288de865533c7a39b81fe1dd51088a1ddc7c33edb0325e12a676153d5563
7
- data.tar.gz: 5e01aeaa4eea9099c47445c88db326ff4ceb5d028088bbd6c2a328f21673c17d80c93bc8fda0c4135e76956f1439a26df758985a23bfe895d5f3f2ff14eb3982
6
+ metadata.gz: a8e8d607f077c5de894d5fd70989d595fda9f4fab5d5111eeedb4acfa526d4f94cc7cb64abc64b42ac2968ef9e7f16bbf413a4329b46e919f0b46d8e9bc5c527
7
+ data.tar.gz: 72a674b922b87067ea2c3276f111603ee377a25bff277862187aa879b11ac83909877749b6b0cd5ff3315429e95b482da211f54b74b460eff129015780734e6c
@@ -0,0 +1,18 @@
1
+ name: Linting
2
+ on: [pull_request]
3
+ jobs:
4
+ rubocop:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v2
8
+ - name: Set up Ruby
9
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
10
+ with:
11
+ ruby-version: 3.0.0
12
+ bundler-cache: true
13
+ - name: Run RuboCop
14
+ uses: reviewdog/action-rubocop@v2
15
+ with:
16
+ rubocop_version: gemfile
17
+ github_token: ${{ secrets.github_token }}
18
+ reporter: github-pr-review
@@ -0,0 +1,17 @@
1
+ name: Testing
2
+ on: [pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ ruby-version: ['2.6', '2.7', '3.0']
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - name: Set up Ruby
12
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
13
+ with:
14
+ ruby-version: ${{ matrix.ruby-version }}
15
+ bundler-cache: true
16
+ - name: Run tests
17
+ run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,12 +1,23 @@
1
1
  require:
2
2
  - rubocop-performance
3
3
 
4
+ inherit_mode:
5
+ merge:
6
+ - Exclude
7
+
4
8
  AllCops:
9
+ NewCops: enable
5
10
  Exclude:
6
11
  - 'bin/**/*'
7
12
  - 'test/mock/app/**/*'
8
13
  - 'test/mock/examples/**/*'
9
14
 
15
+ Gemspec/RequiredRubyVersion:
16
+ Enabled: false
17
+
18
+ Layout/LineLength:
19
+ Max: 120
20
+
10
21
  Metrics/AbcSize:
11
22
  Exclude:
12
23
  - 'test/**/*'
@@ -16,6 +27,9 @@ Metrics/MethodLength:
16
27
  Exclude:
17
28
  - 'test/**/*'
18
29
 
30
+ Metrics/ModuleLength:
31
+ Max: 250
32
+
19
33
  Style/ClassAndModuleChildren:
20
34
  Exclude:
21
35
  - 'test/**/*'
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Chusaku
2
2
 
3
- | Gem | CI | Quality |
4
- |-----|----|---------|
5
- |[![Gem Version](https://badge.fury.io/rb/chusaku.svg)](https://badge.fury.io/rb/chusaku)|[![CircleCI](https://circleci.com/gh/nshki/chusaku.svg?style=svg&circle-token=e1917972632f242932171de0ca5443148e83151c)](https://circleci.com/gh/nshki/chusaku)|[![Maintainability](https://api.codeclimate.com/v1/badges/e21235bd513aadf0407b/maintainability)](https://codeclimate.com/github/nshki/chusaku/maintainability)
3
+ [![Gem Version](https://badge.fury.io/rb/chusaku.svg)](https://badge.fury.io/rb/chusaku)
6
4
 
7
5
  Add comments above your Rails actions that look like:
8
6
 
@@ -54,9 +52,10 @@ Chusaku has some flags available for use as well:
54
52
  ```
55
53
  $ bundle exec chusaku --help
56
54
  Usage: chusaku [options]
55
+ --dry-run Run without file modifications
57
56
  --exit-with-error-on-annotation
58
57
  Fail if any file was annotated
59
- --dry-run Run without file modifications
58
+ --verbose Print all annotations
60
59
  -v, --version Show Chusaku version number and quit
61
60
  -h, --help Show this help message and quit
62
61
  ```
data/chusaku.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.name = 'chusaku'
9
9
  spec.version = Chusaku::VERSION
10
10
  spec.authors = ['Nishiki Liu']
11
- spec.email = ['nishiki.liu@gmail.com']
11
+ spec.email = ['nishiki@hey.com']
12
12
 
13
13
  spec.summary = 'Annotate your Rails controllers with route info.'
14
14
  spec.description = 'Annotate your Rails controllers with route info.'
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.metadata['changelog_uri'] = spec.homepage
27
27
  else
28
28
  raise 'RubyGems 2.0 or newer is required to protect against ' \
29
- 'public gem pushes.'
29
+ 'public gem pushes.'
30
30
  end
31
31
 
32
32
  # Specify which files should be added to the gem when it is released.
@@ -37,15 +37,15 @@ Gem::Specification.new do |spec|
37
37
  f.match(%r{^(test|spec|features)/})
38
38
  end
39
39
  end
40
- spec.bindir = 'bin'
41
- spec.executables = 'chusaku'
40
+ spec.bindir = 'bin'
41
+ spec.executables = 'chusaku'
42
42
  spec.require_paths = ['lib']
43
43
 
44
- spec.add_development_dependency 'bundler', '~> 2.0'
45
- spec.add_development_dependency 'minitest', '~> 5.0'
46
- spec.add_development_dependency 'rake', '~> 12.3'
47
- spec.add_development_dependency 'rubocop', '~> 0.77'
48
- spec.add_development_dependency 'rubocop-performance', '~> 1.5'
44
+ spec.add_development_dependency 'bundler', '~> 2.2'
45
+ spec.add_development_dependency 'minitest', '~> 5.14'
46
+ spec.add_development_dependency 'rake', '~> 13.0'
47
+ spec.add_development_dependency 'rubocop', '~> 1.7'
48
+ spec.add_development_dependency 'rubocop-performance', '~> 1.9'
49
49
 
50
- spec.add_dependency 'rails', '> 2.0'
50
+ spec.add_dependency 'railties', '>= 3.0'
51
51
  end
data/lib/chusaku/cli.rb CHANGED
@@ -14,17 +14,17 @@ module Chusaku
14
14
  Finished = Class.new(RuntimeError)
15
15
  NotARailsProject = Class.new(RuntimeError)
16
16
 
17
- # Initializes a new instance of Chusaku::CLI.
17
+ # Initializes a new instance of `Chusaku::CLI`.
18
18
  #
19
- # @return {Chusaku::CLI} - Instance of this class
19
+ # @return [Chusaku::CLI] Instance of this class
20
20
  def initialize
21
21
  @options = {}
22
22
  end
23
23
 
24
24
  # Parse CLI flags, if any, and handle applicable behaviors.
25
25
  #
26
- # @param {Array<String>} args - CLI arguments
27
- # @return {Integer} - 0 on success, 1 on error
26
+ # @param args [Array<String>] CLI arguments
27
+ # @return [Integer] 0 on success, 1 on error
28
28
  def call(args = ARGV)
29
29
  optparser.parse!(args)
30
30
  check_for_rails_project
@@ -40,8 +40,8 @@ module Chusaku
40
40
 
41
41
  # Raises exception if Rails project cannot be detected.
42
42
  #
43
- # @raise {Chusaku::CLI::NotARailsProject} - Exception if not Rails project
44
- # @return {void}
43
+ # @raise [Chusaku::CLI::NotARailsProject] Exception if not Rails project
44
+ # @return [void]
45
45
  def check_for_rails_project
46
46
  has_controllers = File.directory?('./app/controllers')
47
47
  has_rakefile = File.exist?('./Rakefile')
@@ -50,53 +50,54 @@ module Chusaku
50
50
 
51
51
  # Returns an instance of OptionParser with supported flags.
52
52
  #
53
- # @return {OptionParser} - Preconfigured OptionParser instance
53
+ # @return [OptionParser] Preconfigured OptionParser instance
54
54
  def optparser
55
55
  OptionParser.new do |opts|
56
56
  opts.banner = 'Usage: chusaku [options]'
57
- add_error_on_annotation_flag(opts)
58
57
  add_dry_run_flag(opts)
58
+ add_error_on_annotation_flag(opts)
59
+ add_verbose_flag(opts)
59
60
  add_version_flag(opts)
60
61
  add_help_flag(opts)
61
62
  end
62
63
  end
63
64
 
65
+ # Adds `--dry-run` flag.
66
+ #
67
+ # @param opts [OptionParser] OptionParser instance
68
+ # @return [void]
69
+ def add_dry_run_flag(opts)
70
+ opts.on('--dry-run', 'Run without file modifications') do
71
+ @options[:dry] = true
72
+ end
73
+ end
74
+
64
75
  # Adds `--exit-with-error-on-annotation` flag.
65
76
  #
66
- # @param {OptionParser} opts - OptionParser instance
67
- # @return {void}
77
+ # @param opts [OptionParser] OptionParser instance
78
+ # @return [void]
68
79
  def add_error_on_annotation_flag(opts)
69
- opts.on(
70
- '--exit-with-error-on-annotation',
71
- 'Fail if any file was annotated'
72
- ) do
80
+ opts.on('--exit-with-error-on-annotation', 'Fail if any file was annotated') do
73
81
  @options[:error_on_annotation] = true
74
82
  end
75
83
  end
76
84
 
77
- # Adds `--dry-run` flag.
85
+ # Adds `--verbose` flag.
78
86
  #
79
- # @param {OptionParser} opts - OptionParser instance
80
- # @return {void}
81
- def add_dry_run_flag(opts)
82
- opts.on(
83
- '--dry-run',
84
- 'Run without file modifications'
85
- ) do
86
- @options[:dry] = true
87
+ # @param opts [OptionParser] OptionParser instance
88
+ # @return [void]
89
+ def add_verbose_flag(opts)
90
+ opts.on('--verbose', 'Print all annotations') do
91
+ @options[:verbose] = true
87
92
  end
88
93
  end
89
94
 
90
95
  # Adds `--version` flag.
91
96
  #
92
- # @param {OptionParser} opts - OptionParser instance
93
- # @return {void}
97
+ # @param opts [OptionParser] OptionParser instance
98
+ # @return [void]
94
99
  def add_version_flag(opts)
95
- opts.on(
96
- '-v',
97
- '--version',
98
- 'Show Chusaku version number and quit'
99
- ) do
100
+ opts.on('-v', '--version', 'Show Chusaku version number and quit') do
100
101
  puts(Chusaku::VERSION)
101
102
  raise Finished
102
103
  end
@@ -104,14 +105,10 @@ module Chusaku
104
105
 
105
106
  # Adds `--help` flag.
106
107
  #
107
- # @param {OptionParser} opts - OptionParser instance
108
- # @return {void}
108
+ # @param opts [OptionParser] OptionParser instance
109
+ # @return [void]
109
110
  def add_help_flag(opts)
110
- opts.on(
111
- '-h',
112
- '--help',
113
- 'Show this help message and quit'
114
- ) do
111
+ opts.on('-h', '--help', 'Show this help message and quit') do
115
112
  puts(opts)
116
113
  raise Finished
117
114
  end
@@ -3,6 +3,8 @@
3
3
  module Chusaku
4
4
  # Handles parsing a file and groups its lines into categories.
5
5
  module Parser
6
+ # Primary method to call.
7
+ #
6
8
  # Example output:
7
9
  #
8
10
  # {
@@ -11,45 +13,49 @@ module Chusaku
11
13
  # {
12
14
  # type: :code,
13
15
  # body: 'class Foo\n',
14
- # action: nil
16
+ # action: nil,
17
+ # line_number: 1
15
18
  # },
16
19
  # {
17
20
  # type: :comment,
18
21
  # body: ' # Bar\n # Baz\n',
19
- # action: nil
22
+ # action: nil,
23
+ # line_number: 2
20
24
  # },
21
25
  # {
22
26
  # type: :action,
23
27
  # body: ' def action_name; end\n',
24
- # action: 'action_name'
28
+ # action: 'action_name',
29
+ # line_number: 4
25
30
  # }
26
31
  # {
27
32
  # type: :code,
28
33
  # body: 'end # vanilla is the best flavor\n',
29
- # action: nil
34
+ # action: nil,
35
+ # line_number: 5
30
36
  # }
31
37
  # ]
32
38
  # }
33
39
  #
34
- # @param {String} path - File path to parse
35
- # @param {Array<String>} actions - List of valid actions for this route
36
- # @return {Hash} - { content: String, groups: Array<Hash> }
40
+ # @param path [String] File path to parse
41
+ # @param actions [Array<String>] List of valid actions for this route
42
+ # @return [Hash] { content => String, groups => Array<Hash> }
37
43
  def self.call(path:, actions:)
38
44
  groups = []
39
45
  group = {}
40
46
  content = IO.read(path)
41
47
 
42
- content.each_line do |line|
48
+ content.each_line.with_index do |line, index|
43
49
  parsed_line = parse_line(line: line, actions: actions)
44
50
 
45
- if group[:type] != parsed_line[:type]
51
+ if group[:type] == parsed_line[:type]
52
+ # Same group. Push the current line into the current group.
53
+ group[:body] += line
54
+ else
46
55
  # Now looking at a new group. Push the current group onto the array
47
56
  # and start a new one.
48
57
  groups.push(group) unless group.empty?
49
- group = parsed_line
50
- else
51
- # Same group. Push the current line into the current group.
52
- group[:body] += line
58
+ group = parsed_line.merge(line_number: index + 1)
53
59
  end
54
60
  end
55
61
 
@@ -58,20 +64,22 @@ module Chusaku
58
64
  { content: content, groups: groups }
59
65
  end
60
66
 
61
- # Given a line and actions, returns the line's type:
67
+ # Given a line and actions, returns the line's type.
68
+ #
69
+ # A type can be one of:
62
70
  #
63
71
  # 1. comment - A line that is entirely commented. Lines that have trailing
64
72
  # comments do not fall under this category.
65
73
  # 2. action - A line that contains an action definition.
66
74
  # 3. code - Anything else.
67
75
  #
68
- # And give back a Hash in the form:
76
+ # Returns a Hash in the form:
69
77
  #
70
78
  # { type: :action, body: 'def foo', action: 'foo' }
71
79
  #
72
- # @param {String} line - A line of a file
73
- # @param {Array<String>} actions - List of valid actions for this route
74
- # @return {Hash} - { type: Symbol, body: String, action: String }
80
+ # @param line [String] A line of a file
81
+ # @param actions [Array<String>] List of valid actions for this route
82
+ # @return [Hash] { type => Symbol, body => String, action => String }
75
83
  def self.parse_line(line:, actions:)
76
84
  comment_match = /^\s*#.*$/.match(line)
77
85
  def_match = /^\s*def\s+(\w*)\s*\w*.*$/.match(line)
@@ -4,6 +4,8 @@ module Chusaku
4
4
  # Handles extracting information about the Rails project's routes.
5
5
  class Routes
6
6
  class << self
7
+ # Primary method to call.
8
+ #
7
9
  # Example output:
8
10
  #
9
11
  # {
@@ -23,19 +25,21 @@ module Chusaku
23
25
  # }
24
26
  # }
25
27
  #
26
- # @return {Hash} - Routes hash
28
+ # @return [Hash] Routes hash
27
29
  def call
28
30
  routes = {}
29
31
 
30
32
  Rails.application.routes.routes.each do |route|
31
- controller, action = extract_controller_and_action_from(route)
33
+ controller, action, defaults = extract_data_from(route)
32
34
  routes[controller] ||= {}
33
35
  routes[controller][action] ||= []
34
36
 
35
- verbs_for(route).each do |verb|
36
- routes[controller][action].push(format(route: route, verb: verb))
37
- routes[controller][action].uniq!
38
- end
37
+ add_info_for \
38
+ route: route,
39
+ routes: routes,
40
+ controller: controller,
41
+ action: action,
42
+ defaults: defaults
39
43
  end
40
44
 
41
45
  backfill_routes(routes)
@@ -43,38 +47,56 @@ module Chusaku
43
47
 
44
48
  private
45
49
 
50
+ # Adds formatted route info for the given param combination.
51
+ #
52
+ # @param route [Hash] Route info
53
+ # @param routes [Hash] Collection of all route info
54
+ # @param controller [String] Controller key
55
+ # @param action [String] Action key
56
+ # @param defaults [Hash] Default parameters for route
57
+ # @return [void]
58
+ def add_info_for(route:, routes:, controller:, action:, defaults:)
59
+ verbs_for(route).each do |verb|
60
+ routes[controller][action]
61
+ .push(format(route: route, verb: verb, defaults: defaults))
62
+ routes[controller][action].uniq!
63
+ end
64
+ end
65
+
46
66
  # Extract the HTTP verbs for a Rails route. Required for older versions of
47
67
  # Rails that return regular expressions for a route verb which sometimes
48
68
  # contains multiple verbs.
49
69
  #
50
- # @param {ActionDispatch::Journey::Route} route - Route given by Rails
51
- # @return {Array<String>} - List of HTTP verbs for the given route
70
+ # @param route [ActionDispatch::Journey::Route] Route given by Rails
71
+ # @return [Array<String>] List of HTTP verbs for the given route
52
72
  def verbs_for(route)
53
73
  route_verb = route.verb.to_s
54
74
 
55
- ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].select do |verb|
75
+ %w[GET POST PUT PATCH DELETE].select do |verb|
56
76
  route_verb.include?(verb)
57
77
  end
58
78
  end
59
79
 
60
80
  # Formats information for a given route.
61
81
  #
62
- # @param {ActionDispatch::Journey::Route} route - Route given by Rails
63
- # @param {String} verb - HTTP verb
64
- # @return {Hash} - { verb: String, path: String, name: String }
65
- def format(route:, verb:)
82
+ # @param route [ActionDispatch::Journey::Route] Route given by Rails
83
+ # @param verb [String] HTTP verb
84
+ # @param defaults [Hash] Default parameters for route
85
+ # @return [Hash] { verb => String, path => String, name => String }
86
+ def format(route:, verb:, defaults:)
66
87
  {
67
88
  verb: verb,
68
89
  path: route.path.spec.to_s.gsub('(.:format)', ''),
69
- name: route.name
90
+ name: route.name,
91
+ defaults: defaults
70
92
  }
71
93
  end
72
94
 
73
95
  # Given a routes hash, backfill entries that aren't already filled by
74
96
  # `Rails.application.routes`.
75
97
  #
76
- # @param {Hash} routes - Routes hash generated by this class
77
- # @return {Hash} - Backfilled routes hash
98
+ # @param routes [Hash] Routes hash generated by this class
99
+ # @return [Hash] Backfilled routes hash
78
100
  def backfill_routes(routes)
79
101
  paths = {}
80
102
 
@@ -92,14 +114,14 @@ module Chusaku
92
114
 
93
115
  # Given a route, extract the controller and action strings.
94
116
  #
95
- # @param {ActionDispatch::Journey::Route} route - Route instance
96
- # @return {Array<String>} - [String, String]
97
- def extract_controller_and_action_from(route)
98
- defaults = route.defaults
99
- controller = defaults[:controller]
100
- action = defaults[:action]
117
+ # @param route [ActionDispatch::Journey::Route] Route instance
118
+ # @return [Array<Object>] (String, String, Hash)
119
+ def extract_data_from(route)
120
+ defaults = route.defaults.dup
121
+ controller = defaults.delete(:controller)
122
+ action = defaults.delete(:action)
101
123
 
102
- [controller, action]
124
+ [controller, action, defaults]
103
125
  end
104
126
  end
105
127
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
- VERSION = '0.3.2'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/chusaku.rb CHANGED
@@ -15,16 +15,16 @@ module Chusaku
15
15
  # # ...
16
16
  # end
17
17
  #
18
- # @param {Hash} flags - CLI flags
19
- # @return {Integer} - 0 on success, 1 on error
18
+ # @param flags [Hash] CLI flags
19
+ # @return [Integer] 0 on success, 1 on error
20
20
  def call(flags = {})
21
21
  @flags = flags
22
22
  @routes = Chusaku::Routes.call
23
- @annotated_paths = []
23
+ @changes = []
24
24
  controllers_pattern = 'app/controllers/**/*_controller.rb'
25
25
 
26
26
  Dir.glob(Rails.root.join(controllers_pattern)).each do |path|
27
- controller = %r{controllers\/(.*)_controller\.rb}.match(path)[1]
27
+ controller = %r{controllers/(.*)_controller\.rb}.match(path)[1]
28
28
  actions = @routes[controller]
29
29
  next if actions.nil?
30
30
 
@@ -38,35 +38,63 @@ module Chusaku
38
38
 
39
39
  # Adds annotations to the given file.
40
40
  #
41
- # @param {String} path - Path to file
42
- # @param {String} controller - Controller name
43
- # @param {Array<String>} actions - List of valid actions for the controller
44
- # @return {void}
41
+ # @param path [String] Path to file
42
+ # @param controller [String] Controller name
43
+ # @param actions [Array<String>] List of valid actions for the controller
44
+ # @return [void]
45
45
  def annotate_file(path:, controller:, actions:)
46
46
  parsed_file = Chusaku::Parser.call(path: path, actions: actions)
47
47
  parsed_file[:groups].each_cons(2) do |prev, curr|
48
- clean_group(prev)
48
+ record_change(group: prev, type: :clean, path: path)
49
49
  next unless curr[:type] == :action
50
50
 
51
51
  route_data = @routes[controller][curr[:action]]
52
52
  next unless route_data.any?
53
53
 
54
- annotate_group(group: curr, route_data: route_data)
54
+ record_change(group: curr, type: :annotate, route_data: route_data, path: path)
55
55
  end
56
56
 
57
57
  write_to_file(path: path, parsed_file: parsed_file)
58
58
  end
59
59
 
60
+ # Clean or annotate a group and track the group as changed if applicable.
61
+ #
62
+ # @param group [Hash] { type => Symbol, body => String }
63
+ # @param type [Symbol] [:clean, :annotate]
64
+ # @param path [String] File path
65
+ # @param route_data [Array<Hash>] [{
66
+ # verb: String,
67
+ # path: String,
68
+ # name: String }]
69
+ # @return [void]
70
+ def record_change(group:, type:, path:, route_data: [])
71
+ old_body = group[:body]
72
+
73
+ case type
74
+ when :clean
75
+ clean_group(group)
76
+ when :annotate
77
+ annotate_group(group: group, route_data: route_data)
78
+ end
79
+ return if old_body == group[:body]
80
+
81
+ @changes.push \
82
+ old_body: old_body,
83
+ new_body: group[:body],
84
+ path: path,
85
+ line_number: group[:line_number]
86
+ end
87
+
60
88
  # Given a parsed group, clean out its contents.
61
89
  #
62
- # @param {Hash} group - { type: Symbol, body: String }
90
+ # @param group [Hash] { type => Symbol, body => String }
63
91
  # @return {void}
64
92
  def clean_group(group)
65
93
  return unless group[:type] == :comment
66
94
 
67
95
  group[:body] = group[:body].gsub(/^\s*#\s*@route.*$\n/, '')
68
96
  group[:body] =
69
- group[:body].gsub(%r{^\s*# (GET|POST|PATCH\/PUT|DELETE) \/\S+$\n}, '')
97
+ group[:body].gsub(%r{^\s*# (GET|POST|PATCH/PUT|DELETE) /\S+$\n}, '')
70
98
  end
71
99
 
72
100
  # Add an annotation to the given group given by Chusaku::Parser that looks
@@ -74,57 +102,125 @@ module Chusaku
74
102
  #
75
103
  # @route GET /waterlilies/:id (waterlilies)
76
104
  #
77
- # @param {Hash} group - Parsed content given by Chusaku::Parser
78
- # @param {Hash} route_data - Individual route data given by Chusaku::Routes
79
- # @return {void}
105
+ # @param group [Hash] Parsed content given by Chusaku::Parser
106
+ # @param route_data [Hash] Individual route data given by Chusaku::Routes
107
+ # @return [void]
80
108
  def annotate_group(group:, route_data:)
81
109
  whitespace = /^(\s*).*$/.match(group[:body])[1]
82
110
  route_data.reverse_each do |datum|
83
- name = datum[:name]
84
- annotation = "@route #{datum[:verb]} #{datum[:path]}"
85
- annotation += " (#{name})" unless name.nil?
86
- comment = "#{whitespace}# #{annotation}\n"
111
+ comment = "#{whitespace}# #{annotate_route(**datum)}\n"
87
112
  group[:body] = comment + group[:body]
88
113
  end
89
114
  end
90
115
 
116
+ # Generate route annotation.
117
+ #
118
+ # @param verb [String] HTTP verb for route
119
+ # @param path [String] Rails path for route
120
+ # @param name [String] Name used in route helpers
121
+ # @param defaults [Hash] Default parameters for route
122
+ # @return [String] "@route <verb> <path> {<defaults>} (<name>)"
123
+ def annotate_route(verb:, path:, name:, defaults:)
124
+ annotation = "@route #{verb} #{path}"
125
+ if defaults&.any?
126
+ defaults_str =
127
+ defaults
128
+ .map { |key, value| "#{key}: #{value.inspect}" }
129
+ .join(', ')
130
+ annotation += " {#{defaults_str}}"
131
+ end
132
+ annotation += " (#{name})" unless name.nil?
133
+ annotation
134
+ end
135
+
91
136
  # Write annotated content to a file if it differs from the original.
92
137
  #
93
- # @param {String} path - File path to write to
94
- # @param {Hash} parsed_file - Hash mutated by `annotate_group`
95
- # @return {void}
138
+ # @param path [String] File path to write to
139
+ # @param parsed_file [Hash] Hash mutated by {#annotate_group}
140
+ # @return [void]
96
141
  def write_to_file(path:, parsed_file:)
97
- content = parsed_file[:groups].map { |pf| pf[:body] }.join
98
- return unless parsed_file[:content] != content
99
-
100
- unless @flags.include?(:dry)
101
- # When running the test suite, we want to make sure we're not
102
- # overwriting any files. `r` mode ensures that.
103
- mode = File.instance_methods.include?(:test_write) ? 'r' : 'w'
104
-
105
- File.open(path, mode) do |file|
106
- if file.respond_to?(:test_write)
107
- file.test_write(content, path)
108
- else
109
- file.write(content)
110
- end
142
+ new_content = new_content_for(parsed_file)
143
+ return if parsed_file[:content] == new_content
144
+
145
+ !@flags.include?(:dry) && perform_write(path: path, content: new_content)
146
+ end
147
+
148
+ # Extracts the new file content for the given parsed file.
149
+ #
150
+ # @param parsed_file [Hash] { groups => Array<Hash> }
151
+ # @return [String] New file content
152
+ def new_content_for(parsed_file)
153
+ parsed_file[:groups].map { |pf| pf[:body] }.join
154
+ end
155
+
156
+ # Wraps the write operation. Needed to clearly distinguish whether it's a
157
+ # write in the test suite or a write in actual use.
158
+ #
159
+ # @param path [String] File path
160
+ # @param content [String] File content
161
+ # @return [void]
162
+ def perform_write(path:, content:)
163
+ File.open(path, file_mode) do |file|
164
+ if file.respond_to?(:test_write)
165
+ file.test_write(content, path)
166
+ else
167
+ file.write(content)
111
168
  end
112
169
  end
170
+ end
113
171
 
114
- @annotated_paths.push(path)
172
+ # When running the test suite, we want to make sure we're not overwriting
173
+ # any files. `r` mode ensures that, and `w` is used for actual usage.
174
+ #
175
+ # @return [String] 'r' or 'w'
176
+ def file_mode
177
+ File.instance_methods.include?(:test_write) ? 'r' : 'w'
115
178
  end
116
179
 
117
180
  # Output results to user.
118
181
  #
119
- # @return {Integer} - 0 for success, 1 for error
182
+ # @return [Integer] 0 for success, 1 for error
120
183
  def output_results
121
- if @annotated_paths.any?
122
- puts("Annotated #{@annotated_paths.join(', ')}")
123
- @flags.include?(:error_on_annotation) ? 1 : 0
124
- else
125
- puts('Nothing to annotate')
126
- 0
127
- end
184
+ puts(output_copy)
185
+ exit_code = 0
186
+ exit_code = 1 if @changes.any? && @flags.include?(:error_on_annotation)
187
+ exit_code
188
+ end
189
+
190
+ # Determines the copy to be used in the program output.
191
+ #
192
+ # @return [String] Copy to be outputted to user
193
+ def output_copy
194
+ return 'Nothing to annotate.' if @changes.empty?
195
+
196
+ copy = changes_copy
197
+ copy += "\nChusaku has finished running."
198
+ copy += "\nThis was a dry run so no files were changed." if @flags.include?(:dry)
199
+ copy += "\nExited with status code 1." if @flags.include?(:error_on_annotation)
200
+ copy
201
+ end
202
+
203
+ # Returns the copy for recorded changes if `--verbose` flag is passed.
204
+ #
205
+ # @return [String] Copy of recorded changes
206
+ def changes_copy
207
+ return '' unless @flags.include?(:verbose)
208
+
209
+ @changes.map do |change|
210
+ <<~CHANGE_OUTPUT
211
+ [#{change[:path]}:#{change[:line_number]}]
212
+
213
+ Before:
214
+ ```ruby
215
+ #{change[:old_body].chomp}
216
+ ```
217
+
218
+ After:
219
+ ```ruby
220
+ #{change[:new_body].chomp}
221
+ ```
222
+ CHANGE_OUTPUT
223
+ end.join("\n")
128
224
  end
129
225
  end
130
226
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chusaku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nishiki Liu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-09 00:00:00.000000000 Z
11
+ date: 2021-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,94 +16,94 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: '2.2'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: '2.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '5.0'
33
+ version: '5.14'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '5.0'
40
+ version: '5.14'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '12.3'
47
+ version: '13.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '12.3'
54
+ version: '13.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.77'
61
+ version: '1.7'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.77'
68
+ version: '1.7'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rubocop-performance
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '1.5'
75
+ version: '1.9'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '1.5'
82
+ version: '1.9'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rails
84
+ name: railties
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '2.0'
89
+ version: '3.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '2.0'
96
+ version: '3.0'
97
97
  description: Annotate your Rails controllers with route info.
98
98
  email:
99
- - nishiki.liu@gmail.com
99
+ - nishiki@hey.com
100
100
  executables:
101
101
  - chusaku
102
102
  extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
- - ".circleci/config.yml"
106
- - ".codeclimate.yml"
105
+ - ".github/workflows/linting.yml"
106
+ - ".github/workflows/testing.yml"
107
107
  - ".gitignore"
108
108
  - ".rubocop.yml"
109
109
  - Gemfile
@@ -142,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
142
  - !ruby/object:Gem::Version
143
143
  version: '0'
144
144
  requirements: []
145
- rubygems_version: 3.1.2
145
+ rubygems_version: 3.1.6
146
146
  signing_key:
147
147
  specification_version: 4
148
148
  summary: Annotate your Rails controllers with route info.
data/.circleci/config.yml DELETED
@@ -1,29 +0,0 @@
1
- version: 2
2
- jobs:
3
- build:
4
- working_directory: ~/chusaku
5
- docker:
6
- - image: circleci/ruby
7
- steps:
8
- - checkout
9
- - run:
10
- name: Install Bundler
11
- command: |
12
- gem install bundler
13
- - restore_cache:
14
- keys:
15
- - dep-v1-{{ .Branch }}-{{ checksum "chusaku.gemspec" }}
16
- - dep-v1-{{ .Branch }}
17
- - dep-v1-
18
- - run:
19
- name: Install dependencies
20
- command: |
21
- bundle install --path vendor/bundle
22
- - save_cache:
23
- key: dep-v1-{{ .Branch }}-{{ checksum "chusaku.gemspec" }}
24
- paths:
25
- - vendor/bundle
26
- - run:
27
- name: Run tests
28
- command: |
29
- bundle exec rake test
data/.codeclimate.yml DELETED
@@ -1,4 +0,0 @@
1
- plugins:
2
- rubocop:
3
- enabled: true
4
- channel: rubocop-0-77