chusaku 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be8c03a0fe70a6b717b8fb204ae3d2d1b9eaaa89597c7ada8c169cb49919d2ef
4
- data.tar.gz: 28736b04b639cba7eed34695691433a0cf4defd3f48eaf7a15faced2e04540e8
3
+ metadata.gz: ed6577989b6ceef5334bd9217f5128b9178792f9b633c7ec7f284263f64cd415
4
+ data.tar.gz: a50d72e4e772fc7848606ef5d6772ce434c881d71fe6dc3a4cff928cceb188db
5
5
  SHA512:
6
- metadata.gz: 178db8776a7bfeaccbe2eea70346753cf203531c7836f706988a65c3527ac6de733580ed6e324f93658fbbeec4812fff9196f460985abdab140eea99e794ab61
7
- data.tar.gz: f8307518e4df5d74a3c5703efed73f167138c04ae2a0d2a1f314c16bc0b1e6d101c64355d994349dc238be2a486745dbea4703389bf485dbb681412e03ae21f8
6
+ metadata.gz: da939ea2a2d76601994dee78c46ece98f2b03d2f7740f93b0d990972693a97a2125910c8a3b9bd035410088fd29ad13f65010afb7b37c1bafc48329601812294
7
+ data.tar.gz: c73cbde3d26058fb72795513d081159e5d5e43beecd8903ebf2c19b40b71b06b76ac3c381ab26485ae08d7ac9fca30f4246c51ee9d3b0cc445baa54d08511403
data/.codeclimate.yml ADDED
@@ -0,0 +1,4 @@
1
+ plugins:
2
+ rubocop:
3
+ enabled: true
4
+ channel: rubocop-0-77
data/.rubocop.yml CHANGED
@@ -1,27 +1,27 @@
1
+ require:
2
+ - rubocop-performance
3
+
1
4
  AllCops:
2
5
  Exclude:
3
6
  - 'bin/**/*'
7
+ - 'test/mock/app/**/*'
8
+ - 'test/mock/examples/**/*'
4
9
 
5
- Layout/IndentationConsistency:
6
- EnforcedStyle: rails
10
+ Layout/LineLength:
11
+ Max: 120
7
12
 
8
13
  Metrics/AbcSize:
9
- Enabled: false
14
+ Exclude:
15
+ - 'test/**/*'
10
16
 
11
17
  Metrics/MethodLength:
12
- Enabled: false
13
-
14
- Metrics/PerceivedComplexity:
15
- Enabled: false
16
-
17
- Style/ClassVars:
18
- Enabled: false
19
-
20
- Style/CommentedKeyword:
21
- Enabled: false
18
+ Max: 25
19
+ Exclude:
20
+ - 'test/**/*'
22
21
 
23
- Style/Documentation:
24
- Enabled: false
22
+ Metrics/ModuleLength:
23
+ Max: 250
25
24
 
26
- Style/RegexpLiteral:
27
- Enabled: false
25
+ Style/ClassAndModuleChildren:
26
+ Exclude:
27
+ - 'test/**/*'
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Chusaku
2
2
 
3
- | Build | Gem |
4
- |-------|-----|
5
- |[![CircleCI](https://circleci.com/gh/nshki/chusaku.svg?style=svg&circle-token=e1917972632f242932171de0ca5443148e83151c)](https://circleci.com/gh/nshki/chusaku)|[![Gem Version](https://badge.fury.io/rb/chusaku.svg)](https://badge.fury.io/rb/chusaku)|
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)
6
6
 
7
7
  Add comments above your Rails actions that look like:
8
8
 
@@ -61,8 +61,20 @@ Usage: chusaku [options]
61
61
  -h, --help Show this help message and quit
62
62
  ```
63
63
 
64
- If you'd like to use Chusaku as a Git hook, take a look at
65
- [Lefthook](https://github.com/Arkweid/lefthook).
64
+
65
+ ## Pre-commit Hook
66
+
67
+ Here's an example setup that you could use for automating Chusaku as a Git hook
68
+ with the [Lefthook](https://github.com/Arkweid/lefthook) gem.
69
+
70
+ ```yaml
71
+ pre-commit:
72
+ commands:
73
+ chusaku:
74
+ run: eval "! git diff --staged --name-only | grep -q 'routes.rb' && exit 0 || bundle exec chusaku --exit-with-error-on-annotation"
75
+ ```
76
+
77
+ This example config only runs Chusaku if `routes.rb` was modified.
66
78
 
67
79
 
68
80
  ## Development
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.'
@@ -37,13 +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', '~> 10.0'
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'
47
49
 
48
- spec.add_dependency 'rails', '> 2.0'
50
+ spec.add_dependency 'railties', '>= 3.0'
49
51
  end
data/lib/chusaku.rb CHANGED
@@ -4,106 +4,163 @@ require 'chusaku/version'
4
4
  require 'chusaku/parser'
5
5
  require 'chusaku/routes'
6
6
 
7
+ # Handles core functionality of annotating projects.
7
8
  module Chusaku
8
- # The main method to run Chusaku. Annotate all actions in your Rails project
9
- # as follows:
10
- #
11
- # # @route GET /waterlilies/:id (waterlilies)
12
- # def show
13
- # # ...
14
- # end
15
- #
16
- # @param {Array<String>} args - CLI flags
17
- # @return {Integer} 0 on success, 1 on error
18
- def self.call(args = [])
19
- routes = Chusaku::Routes.call
20
- controller_pattern = 'app/controllers/**/*_controller.rb'
21
- controller_paths = Dir.glob(Rails.root.join(controller_pattern))
22
- annotated_paths = []
23
-
24
- # Loop over all controller file paths.
25
- controller_paths.each do |path|
26
- controller = /controllers\/(.*)_controller\.rb/.match(path)[1]
27
- actions = routes[controller]
28
- next if actions.nil?
29
-
30
- # Parse the file and iterate over the parsed content, two entries at a
31
- # time.
32
- parsed_file = Chusaku::Parser.call(path: path, actions: actions.keys)
33
- parsed_file[:groups].each_cons(2) do |prev, curr|
34
- # Remove all @route comments in the previous group.
35
- if prev[:type] == :comment
36
- prev[:body] = prev[:body].gsub(/^\s*#\s*@route.*$\n/, '')
37
- end
9
+ class << self
10
+ # The main method to run Chusaku. Annotate all actions in a Rails project as
11
+ # follows:
12
+ #
13
+ # # @route GET /waterlilies/:id (waterlilies)
14
+ # def show
15
+ # # ...
16
+ # end
17
+ #
18
+ # @param flags [Hash] CLI flags
19
+ # @return [Integer] 0 on success, 1 on error
20
+ def call(flags = {})
21
+ @flags = flags
22
+ @routes = Chusaku::Routes.call
23
+ @annotated_paths = []
24
+ controllers_pattern = 'app/controllers/**/*_controller.rb'
25
+
26
+ Dir.glob(Rails.root.join(controllers_pattern)).each do |path|
27
+ controller = %r{controllers\/(.*)_controller\.rb}.match(path)[1]
28
+ actions = @routes[controller]
29
+ next if actions.nil?
30
+
31
+ annotate_file(path: path, controller: controller, actions: actions.keys)
32
+ end
38
33
 
39
- # Only proceed if we are currently looking at an action.
34
+ output_results
35
+ end
36
+
37
+ private
38
+
39
+ # Adds annotations to the given file.
40
+ #
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
+ def annotate_file(path:, controller:, actions:)
46
+ parsed_file = Chusaku::Parser.call(path: path, actions: actions)
47
+ parsed_file[:groups].each_cons(2) do |prev, curr|
48
+ clean_group(prev)
40
49
  next unless curr[:type] == :action
41
50
 
42
- # Fetch current action in routes.
43
- action = curr[:action]
44
- data = routes[controller][action]
45
- next unless data.any?
46
-
47
- # Add annotations.
48
- whitespace = /^(\s*).*$/.match(curr[:body])[1]
49
- data.reverse.each do |datum|
50
- annotation = annotate(datum)
51
- comment = "#{whitespace}# #{annotation}\n"
52
- curr[:body] = comment + curr[:body]
53
- end
51
+ route_data = @routes[controller][curr[:action]]
52
+ next unless route_data.any?
53
+
54
+ annotate_group(group: curr, route_data: route_data)
54
55
  end
55
56
 
56
- # Write to file.
57
- parsed_content = parsed_file[:groups].map { |pf| pf[:body] }
58
- new_content = parsed_content.join
59
- if parsed_file[:content] != new_content
60
- write(path, new_content) unless args.include?(:dry)
61
- annotated_paths << path
57
+ write_to_file(path: path, parsed_file: parsed_file)
58
+ end
59
+
60
+ # Given a parsed group, clean out its contents.
61
+ #
62
+ # @param group [Hash] { type => Symbol, body => String }
63
+ # @return {void}
64
+ def clean_group(group)
65
+ return unless group[:type] == :comment
66
+
67
+ group[:body] = group[:body].gsub(/^\s*#\s*@route.*$\n/, '')
68
+ group[:body] =
69
+ group[:body].gsub(%r{^\s*# (GET|POST|PATCH\/PUT|DELETE) \/\S+$\n}, '')
70
+ end
71
+
72
+ # Add an annotation to the given group given by Chusaku::Parser that looks
73
+ # like:
74
+ #
75
+ # @route GET /waterlilies/:id (waterlilies)
76
+ #
77
+ # @param group [Hash] Parsed content given by Chusaku::Parser
78
+ # @param route_data [Hash] Individual route data given by Chusaku::Routes
79
+ # @return [void]
80
+ def annotate_group(group:, route_data:)
81
+ whitespace = /^(\s*).*$/.match(group[:body])[1]
82
+ route_data.reverse_each do |datum|
83
+ comment = "#{whitespace}# #{annotate_route(**datum)}\n"
84
+ group[:body] = comment + group[:body]
62
85
  end
63
86
  end
64
87
 
65
- # Output results to user.
66
- if annotated_paths.any?
67
- puts "Annotated #{annotated_paths.join(', ')}"
68
- if args.include?(:error_on_annotation)
69
- 1
70
- else
71
- 0
88
+ # Generate route annotation.
89
+ #
90
+ # @param verb [String] HTTP verb for route
91
+ # @param path [String] Rails path for route
92
+ # @param name [String] Name used in route helpers
93
+ # @param defaults [Hash] Default parameters for route
94
+ # @return [String] "@route <verb> <path> {<defaults>} (<name>)"
95
+ def annotate_route(verb:, path:, name:, defaults:)
96
+ annotation = "@route #{verb} #{path}"
97
+ if defaults&.any?
98
+ defaults_str =
99
+ defaults
100
+ .map { |key, value| "#{key}: #{value.inspect}" }
101
+ .join(', ')
102
+ annotation += " {#{defaults_str}}"
72
103
  end
73
- else
74
- puts "Nothing to annotate"
75
- 0
104
+ annotation += " (#{name})" unless name.nil?
105
+ annotation
76
106
  end
77
- end
78
107
 
79
- # Write given content to a file. If we're using an overridden version of File,
80
- # then use its method instead for testing purposes.
81
- #
82
- # @param {String} path - File path to write to
83
- # @param {String} content - Contents of the file
84
- # @return {void}
85
- def self.write(path, content)
86
- File.open(path, 'r+') do |file|
87
- if file.respond_to?(:test_write)
88
- file.test_write(content, path)
89
- else
90
- file.write(content)
108
+ # Write annotated content to a file if it differs from the original.
109
+ #
110
+ # @param path [String] File path to write to
111
+ # @param parsed_file [Hash] Hash mutated by {#annotate_group}
112
+ # @return [void]
113
+ def write_to_file(path:, parsed_file:)
114
+ new_content = new_content_for(parsed_file)
115
+ return unless parsed_file[:content] != new_content
116
+
117
+ !@flags.include?(:dry) && perform_write(path: path, content: new_content)
118
+ @annotated_paths.push(path)
119
+ end
120
+
121
+ # Extracts the new file content for the given parsed file.
122
+ #
123
+ # @param parsed_file [Hash] { groups => Array<Hash> }
124
+ # @return [String] New file content
125
+ def new_content_for(parsed_file)
126
+ parsed_file[:groups].map { |pf| pf[:body] }.join
127
+ end
128
+
129
+ # Wraps the write operation. Needed to clearly distinguish whether it's a
130
+ # write in the test suite or a write in actual use.
131
+ #
132
+ # @param path [String] File path
133
+ # @param content [String] File content
134
+ # @return [void]
135
+ def perform_write(path:, content:)
136
+ File.open(path, file_mode) do |file|
137
+ if file.respond_to?(:test_write)
138
+ file.test_write(content, path)
139
+ else
140
+ file.write(content)
141
+ end
91
142
  end
92
143
  end
93
- end
94
144
 
95
- # Given a hash describing an action, generate an annotation in the form:
96
- #
97
- # @route GET /waterlilies/:id (waterlilies)
98
- #
99
- # @param {Hash} action_info - Parsed line given by Chusaku::Parser
100
- # @return {String} Annotation for given parsed line
101
- def self.annotate(action_info)
102
- verb = action_info[:verb]
103
- path = action_info[:path]
104
- name = action_info[:name]
105
- annotation = "@route #{verb} #{path}"
106
- annotation += " (#{name})" unless name.nil?
107
- annotation
145
+ # When running the test suite, we want to make sure we're not overwriting
146
+ # any files. `r` mode ensures that, and `w` is used for actual usage.
147
+ #
148
+ # @return [String] 'r' or 'w'
149
+ def file_mode
150
+ File.instance_methods.include?(:test_write) ? 'r' : 'w'
151
+ end
152
+
153
+ # Output results to user.
154
+ #
155
+ # @return [Integer] 0 for success, 1 for error
156
+ def output_results
157
+ if @annotated_paths.any?
158
+ puts("Annotated #{@annotated_paths.join(', ')}")
159
+ @flags.include?(:error_on_annotation) ? 1 : 0
160
+ else
161
+ puts('Nothing to annotate')
162
+ 0
163
+ end
164
+ end
108
165
  end
109
166
  end
data/lib/chusaku/cli.rb CHANGED
@@ -4,26 +4,27 @@ require 'optparse'
4
4
  require 'chusaku'
5
5
 
6
6
  module Chusaku
7
+ # Enables flags for the `chusaku` executable.
7
8
  class CLI
9
+ attr_reader :options
10
+
8
11
  STATUS_SUCCESS = 0
9
12
  STATUS_ERROR = 1
10
13
 
11
- attr_reader :options
12
-
13
14
  Finished = Class.new(RuntimeError)
14
15
  NotARailsProject = Class.new(RuntimeError)
15
16
 
16
- # Initializes a new instance of Chusaku::CLI.
17
+ # Initializes a new instance of `Chusaku::CLI`.
17
18
  #
18
- # @return {Chusaku::CLI} Instance of this class
19
+ # @return [Chusaku::CLI] Instance of this class
19
20
  def initialize
20
21
  @options = {}
21
22
  end
22
23
 
23
24
  # Parse CLI flags, if any, and handle applicable behaviors.
24
25
  #
25
- # @param {Array<String>} args - CLI arguments
26
- # @return {Integer} 0 on success, 1 on error
26
+ # @param args [Array<String>] CLI arguments
27
+ # @return [Integer] 0 on success, 1 on error
27
28
  def call(args = ARGV)
28
29
  optparser.parse!(args)
29
30
  check_for_rails_project
@@ -37,55 +38,83 @@ module Chusaku
37
38
 
38
39
  private
39
40
 
40
- # Raises exception if Rails project cannot be detected.
41
- #
42
- # @raise {Chusaku::CLI::NotARailsProject} Exception if not Rails project
43
- # @return {void}
44
- def check_for_rails_project
45
- unless File.directory?('./app/controllers') && File.exist?('./Rakefile')
46
- raise NotARailsProject
47
- end
48
- end
41
+ # Raises exception if Rails project cannot be detected.
42
+ #
43
+ # @raise [Chusaku::CLI::NotARailsProject] Exception if not Rails project
44
+ # @return [void]
45
+ def check_for_rails_project
46
+ has_controllers = File.directory?('./app/controllers')
47
+ has_rakefile = File.exist?('./Rakefile')
48
+ raise NotARailsProject unless has_controllers && has_rakefile
49
+ end
49
50
 
50
- # Returns an instance of OptionParser with supported flags.
51
- #
52
- # @return {OptionParser} Preconfigured OptionParser instance
53
- def optparser
54
- OptionParser.new do |opts|
55
- opts.banner = 'Usage: chusaku [options]'
51
+ # Returns an instance of OptionParser with supported flags.
52
+ #
53
+ # @return [OptionParser] Preconfigured OptionParser instance
54
+ def optparser
55
+ OptionParser.new do |opts|
56
+ opts.banner = 'Usage: chusaku [options]'
57
+ add_error_on_annotation_flag(opts)
58
+ add_dry_run_flag(opts)
59
+ add_version_flag(opts)
60
+ add_help_flag(opts)
61
+ end
62
+ end
56
63
 
57
- opts.on(
58
- '--exit-with-error-on-annotation',
59
- 'Fail if any file was annotated'
60
- ) do
61
- @options[:error_on_annotation] = true
62
- end
64
+ # Adds `--exit-with-error-on-annotation` flag.
65
+ #
66
+ # @param opts [OptionParser] OptionParser instance
67
+ # @return [void]
68
+ 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
73
+ @options[:error_on_annotation] = true
74
+ end
75
+ end
63
76
 
64
- opts.on(
65
- '--dry-run',
66
- 'Run without file modifications'
67
- ) do
68
- @options[:dry] = true
69
- end
77
+ # Adds `--dry-run` flag.
78
+ #
79
+ # @param opts [OptionParser] 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
+ end
88
+ end
70
89
 
71
- opts.on(
72
- '-v',
73
- '--version',
74
- 'Show Chusaku version number and quit'
75
- ) do
76
- puts(Chusaku::VERSION)
77
- raise Finished
78
- end
90
+ # Adds `--version` flag.
91
+ #
92
+ # @param opts [OptionParser] OptionParser instance
93
+ # @return [void]
94
+ def add_version_flag(opts)
95
+ opts.on(
96
+ '-v',
97
+ '--version',
98
+ 'Show Chusaku version number and quit'
99
+ ) do
100
+ puts(Chusaku::VERSION)
101
+ raise Finished
102
+ end
103
+ end
79
104
 
80
- opts.on(
81
- '-h',
82
- '--help',
83
- 'Show this help message and quit'
84
- ) do
85
- puts(opts)
86
- raise Finished
87
- end
88
- end
105
+ # Adds `--help` flag.
106
+ #
107
+ # @param opts [OptionParser] OptionParser instance
108
+ # @return [void]
109
+ def add_help_flag(opts)
110
+ opts.on(
111
+ '-h',
112
+ '--help',
113
+ 'Show this help message and quit'
114
+ ) do
115
+ puts(opts)
116
+ raise Finished
89
117
  end
118
+ end
90
119
  end
91
120
  end
@@ -1,27 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
+ # Handles parsing a file and groups its lines into categories.
4
5
  module Parser
5
- # Parses a file and groups its lines into categories:
6
+ # Primary method to call.
6
7
  #
7
8
  # Example output:
8
9
  #
9
- # [ { type: :code,
10
- # body: 'class Foo\n',
11
- # action: nil },
12
- # { type: :comment,
13
- # body: ' # Bar\n # Baz\n',
14
- # action: nil },
15
- # { type: :action,
16
- # body: ' def action_name; end\n',
17
- # action: 'action_name' }
18
- # { type: :code,
19
- # body: 'end # vanilla is the best flavor\n',
20
- # action: nil } ]
10
+ # {
11
+ # content: <Original file content>,
12
+ # groups: [
13
+ # {
14
+ # type: :code,
15
+ # body: 'class Foo\n',
16
+ # action: nil
17
+ # },
18
+ # {
19
+ # type: :comment,
20
+ # body: ' # Bar\n # Baz\n',
21
+ # action: nil
22
+ # },
23
+ # {
24
+ # type: :action,
25
+ # body: ' def action_name; end\n',
26
+ # action: 'action_name'
27
+ # }
28
+ # {
29
+ # type: :code,
30
+ # body: 'end # vanilla is the best flavor\n',
31
+ # action: nil
32
+ # }
33
+ # ]
34
+ # }
21
35
  #
22
- # @param {String} path - File path to parse
23
- # @param {Array<String>} actions - List of valid actions for this route
24
- # @return {Hash} Parsed groups of the file and original content
36
+ # @param path [String] File path to parse
37
+ # @param actions [Array<String>] List of valid actions for this route
38
+ # @return [Hash] { content => String, groups => Array<Hash> }
25
39
  def self.call(path:, actions:)
26
40
  groups = []
27
41
  group = {}
@@ -43,26 +57,25 @@ module Chusaku
43
57
 
44
58
  # Push the last group onto the array and return.
45
59
  groups.push(group)
46
- {
47
- content: content,
48
- groups: groups
49
- }
60
+ { content: content, groups: groups }
50
61
  end
51
62
 
52
- # Given a line and actions, returns the line's type:
63
+ # Given a line and actions, returns the line's type.
64
+ #
65
+ # A type can be one of:
53
66
  #
54
67
  # 1. comment - A line that is entirely commented. Lines that have trailing
55
68
  # comments do not fall under this category.
56
69
  # 2. action - A line that contains an action definition.
57
70
  # 3. code - Anything else.
58
71
  #
59
- # And give back a Hash in the form:
72
+ # Returns a Hash in the form:
60
73
  #
61
74
  # { type: :action, body: 'def foo', action: 'foo' }
62
75
  #
63
- # @param {String} line - A line of a file
64
- # @param {Array<String>} actions - List of valid actions for this route
65
- # @return {Hash} Parsed line
76
+ # @param line [String] A line of a file
77
+ # @param actions [Array<String>] List of valid actions for this route
78
+ # @return [Hash] { type => Symbol, body => String, action => String }
66
79
  def self.parse_line(line:, actions:)
67
80
  comment_match = /^\s*#.*$/.match(line)
68
81
  def_match = /^\s*def\s+(\w*)\s*\w*.*$/.match(line)
@@ -1,62 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
- module Routes
5
- # Extract information about the Rails project's routes.
6
- #
7
- # Example output:
8
- #
9
- # {
10
- # 'users' => {
11
- # 'edit' => [
12
- # { verb: 'GET', path: '/users/:id', name: 'edit_user' }],
13
- # 'update' => [
14
- # { verb: 'PATCH', path: '/users', name: 'edit_user' },
15
- # { verb: 'PUT', path: '/users', name: 'edit_user' }]
16
- # },
17
- # 'empanadas' => {
18
- # 'create' => [
19
- # { verb: 'POST', path: '/empanadas', name: nil }]
20
- # }
21
- # }
22
- #
23
- # @return {Hash} Routes hash
24
- def self.call
25
- routes = {}
4
+ # Handles extracting information about the Rails project's routes.
5
+ class Routes
6
+ class << self
7
+ # Primary method to call.
8
+ #
9
+ # Example output:
10
+ #
11
+ # {
12
+ # 'users' => {
13
+ # 'edit' => [
14
+ # { verb: 'GET', path: '/users/:id', name: 'edit_user' }
15
+ # ],
16
+ # 'update' => [
17
+ # { verb: 'PATCH', path: '/users', name: 'edit_user' },
18
+ # { verb: 'PUT', path: '/users', name: 'edit_user' }
19
+ # ]
20
+ # },
21
+ # 'empanadas' => {
22
+ # 'create' => [
23
+ # { verb: 'POST', path: '/empanadas', name: nil }
24
+ # ]
25
+ # }
26
+ # }
27
+ #
28
+ # @return [Hash] Routes hash
29
+ def call
30
+ routes = {}
26
31
 
27
- Rails.application.routes.routes.each do |route|
28
- defaults = route.defaults
29
- controller = defaults[:controller]
30
- action = defaults[:action]
32
+ Rails.application.routes.routes.each do |route|
33
+ controller, action, defaults = extract_data_from(route)
34
+ routes[controller] ||= {}
35
+ routes[controller][action] ||= []
36
+
37
+ add_info_for \
38
+ route: route,
39
+ routes: routes,
40
+ controller: controller,
41
+ action: action,
42
+ defaults: defaults
43
+ end
31
44
 
32
- routes[controller] ||= {}
33
- routes[controller][action] ||= []
34
- routes[controller][action].push(format_route(route))
45
+ backfill_routes(routes)
35
46
  end
36
47
 
37
- backfill_routes(routes)
38
- end
48
+ private
39
49
 
40
- private
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
41
65
 
42
- # Extract information of a given route.
66
+ # Extract the HTTP verbs for a Rails route. Required for older versions of
67
+ # Rails that return regular expressions for a route verb which sometimes
68
+ # contains multiple verbs.
43
69
  #
44
- # @param {ActionDispatch::Journey::Route} route - Route given by Rails
45
- # @return {Hash} Formatted hash for given route
46
- def self.format_route(route)
70
+ # @param route [ActionDispatch::Journey::Route] Route given by Rails
71
+ # @return [Array<String>] List of HTTP verbs for the given route
72
+ def verbs_for(route)
73
+ route_verb = route.verb.to_s
74
+
75
+ %w[GET POST PUT PATCH DELETE].select do |verb|
76
+ route_verb.include?(verb)
77
+ end
78
+ end
79
+
80
+ # Formats information for a given route.
81
+ #
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:)
47
87
  {
48
- verb: route.verb,
88
+ verb: verb,
49
89
  path: route.path.spec.to_s.gsub('(.:format)', ''),
50
- name: route.name
90
+ name: route.name,
91
+ defaults: defaults
51
92
  }
52
93
  end
53
94
 
54
95
  # Given a routes hash, backfill entries that aren't already filled by
55
96
  # `Rails.application.routes`.
56
97
  #
57
- # @param {Hash} routes - Routes hash generated by this class
58
- # @return {Hash} Backfilled routes
59
- def self.backfill_routes(routes)
98
+ # @param routes [Hash] Routes hash generated by this class
99
+ # @return [Hash] Backfilled routes hash
100
+ def backfill_routes(routes)
60
101
  paths = {}
61
102
 
62
103
  routes.each do |_controller, actions|
@@ -70,5 +111,18 @@ module Chusaku
70
111
 
71
112
  routes
72
113
  end
114
+
115
+ # Given a route, extract the controller and action strings.
116
+ #
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)
123
+
124
+ [controller, action, defaults]
125
+ end
126
+ end
73
127
  end
74
128
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chusaku
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.1'
5
5
  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.2.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nishiki Liu
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-27 00:00:00.000000000 Z
11
+ date: 2021-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,65 +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: '10.0'
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: '10.0'
54
+ version: '13.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rails
56
+ name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">"
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-performance
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: railties
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
60
88
  - !ruby/object:Gem::Version
61
- version: '2.0'
89
+ version: '3.0'
62
90
  type: :runtime
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
- - - ">"
94
+ - - ">="
67
95
  - !ruby/object:Gem::Version
68
- version: '2.0'
96
+ version: '3.0'
69
97
  description: Annotate your Rails controllers with route info.
70
98
  email:
71
- - nishiki.liu@gmail.com
99
+ - nishiki@hey.com
72
100
  executables:
73
101
  - chusaku
74
102
  extensions: []
75
103
  extra_rdoc_files: []
76
104
  files:
77
105
  - ".circleci/config.yml"
106
+ - ".codeclimate.yml"
78
107
  - ".gitignore"
79
108
  - ".rubocop.yml"
80
109
  - Gemfile
@@ -98,7 +127,7 @@ metadata:
98
127
  homepage_uri: https://github.com/nshki/chusaku
99
128
  source_code_uri: https://github.com/nshki/chusaku
100
129
  changelog_uri: https://github.com/nshki/chusaku
101
- post_install_message:
130
+ post_install_message:
102
131
  rdoc_options: []
103
132
  require_paths:
104
133
  - lib
@@ -113,8 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
142
  - !ruby/object:Gem::Version
114
143
  version: '0'
115
144
  requirements: []
116
- rubygems_version: 3.0.3
117
- signing_key:
145
+ rubygems_version: 3.2.3
146
+ signing_key:
118
147
  specification_version: 4
119
148
  summary: Annotate your Rails controllers with route info.
120
149
  test_files: []