chusaku 0.3.2 → 0.6.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: 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