loose_erbs 0.0.1 → 0.0.3

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: a199c48574375f7ba4180d6b80b2f5bac67ed8a9a7c77f12c57d5a2fb7f1d53a
4
- data.tar.gz: cd693a62b42d1f2eec23ce25bec589b03586da8ae646d9d0a5a478a02e540e36
3
+ metadata.gz: 44d3446a83d21a94d64e15ad5fc6cd41541d1593c38ea6ab9ef4e36ac52e1745
4
+ data.tar.gz: 8529f0d1f25cf62c318b13314fc2fa2ef2bbcf2516ec87f45102c84277426b20
5
5
  SHA512:
6
- metadata.gz: 5678e042d65edac1b9868f7d2438860973f3830d6766f943e81e234af9dfcaf1065f9f0034be43221de7e72a3dc9037130cdac84a5c43406da91334cad52d71e
7
- data.tar.gz: b96dda47a01a131505b6585fbc87f43cab4e93df845d096ee61983577f17b71919717ea3f3a0713f76bc994165bd9624c38797e1389f56d778d34e9a90092509
6
+ metadata.gz: 632d54cf79d18b1bf0a8ed8af9b24c2f19a0f5cfa5f545204a1d3c204596daa5724271684da3cf758dddaaf8588e5b27ed1c4f49635bebcb66d201f02f4d9683
7
+ data.tar.gz: 7281ceae45fb7f0d73a413d15592c9a9ec032af02b373e77497f7b5f87be450946f7e3825f20e572eadf362c374f02993b04a21d43cda645d5dbb78975e80f97
data/.rubocop.yml ADDED
@@ -0,0 +1,6 @@
1
+ inherit_gem:
2
+ rubocop-rails_config:
3
+ - config/rails.yml
4
+
5
+ Rails/RefuteMethods:
6
+ Enabled: false
data/README.md CHANGED
@@ -1,35 +1,57 @@
1
- # LooseErbs
1
+ # Loose ERBs
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/loose_erbs`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ A tool to help find Loose ERBs in your app!
6
4
 
7
5
  ## Installation
8
6
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
-
11
7
  Install the gem and add to the application's Gemfile by executing:
12
8
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
9
+ $ bundle add loose_erbs
14
10
 
15
11
  If bundler is not being used to manage dependencies, install the gem by executing:
16
12
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
13
+ $ gem install loose_erbs
18
14
 
19
15
  ## Usage
20
16
 
21
- TODO: Write usage instructions here
17
+ ```shell
18
+ $ loose_erbs --all --trees
19
+ /home/hartley/test/dm/app/views/layouts/application.html.erb
20
+
21
+ /home/hartley/test/dm/app/views/posts/edit.html.erb
22
+ └── /home/hartley/test/dm/app/views/posts/_form.html.erb
23
+
24
+ /home/hartley/test/dm/app/views/posts/index.html.erb
25
+ └── /home/hartley/test/dm/app/views/posts/_post.html.erb
26
+
27
+ /home/hartley/test/dm/app/views/posts/new.html.erb
28
+ └── /home/hartley/test/dm/app/views/posts/_form.html.erb
29
+
30
+ /home/hartley/test/dm/app/views/posts/show.html.erb
31
+ └── /home/hartley/test/dm/app/views/posts/_post.html.erb
32
+
33
+ /home/hartley/test/dm/app/views/pwa/manifest.json.erb
34
+ ```
22
35
 
23
36
  ## Development
24
37
 
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
38
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
39
+ `rake test` to run the tests. You can also run `bin/console` for an interactive
40
+ prompt that will allow you to experiment.
26
41
 
27
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
42
+ To install this gem onto your local machine, run `bundle exec rake install`. To
43
+ release a new version, update the version number in `version.rb`, and then run
44
+ `bundle exec rake release`, which will create a git tag for the version, push
45
+ git commits and the created tag, and push the `.gem` file to
46
+ [rubygems.org](https://rubygems.org).
28
47
 
29
48
  ## Contributing
30
49
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/loose_erbs.
50
+ Bug reports and pull requests are welcome on GitHub at
51
+ https://github.com/skipkayhil/loose_erbs.
32
52
 
33
53
  ## License
34
54
 
35
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
55
+ The gem is available as open source under the terms of the [MIT License][].
56
+
57
+ [MIT License]: https://opensource.org/licenses/MIT
data/Rakefile CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
+ require "rubocop/rake_task"
5
6
 
6
7
  Minitest::TestTask.create
8
+ RuboCop::RakeTask.new
7
9
 
8
- task default: :test
10
+ task default: [:test, :rubocop]
data/exe/loose_erbs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "loose_erbs"
4
5
 
5
- LooseErbs::Cli.new.run
6
+ exit(LooseErbs::Cli.new(ARGV).run)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LooseErbs
4
+ class Registry
5
+ def initialize(view_paths)
6
+ @map = {}
7
+ @view_paths = view_paths
8
+
9
+ @view_paths.each do |view_path|
10
+ Dir["#{view_path}/**/*.erb"].each { register _1 }
11
+ end
12
+ end
13
+
14
+ def dependencies_for(identifier)
15
+ template = @map.fetch(identifier)
16
+
17
+ LooseErbs.parser_class.call(identifier, template, @view_paths).uniq.map { lookup(_1) }
18
+ end
19
+
20
+ # this feels like a hack around not using view paths...
21
+ def lookup(pathish)
22
+ wrapper = Pathname.new(pathish)
23
+
24
+ if wrapper.absolute?
25
+ # /home/hartley/test/dm/app/views/posts/form
26
+ partial_path = wrapper.join("../_#{wrapper.basename}.html.erb").to_s
27
+
28
+ return partial_path if @map.key? partial_path
29
+
30
+ # The DependencyTracker always puts the partial under the parent's
31
+ # namespace even if it could be found under a controller's super's
32
+ # namespace.
33
+ @view_paths.each do |view_path|
34
+ partial_path = Pathname.new(view_path).join("application/_#{wrapper.basename}.html.erb").to_s
35
+
36
+ return partial_path if @map.key? partial_path
37
+ end
38
+ else
39
+ @view_paths.each do |view_path|
40
+ # DependencyTracker removes the _ from partial pathishs
41
+ # posts/post
42
+ partial_path = Pathname.new(view_path).join(pathish).join("../_#{wrapper.basename}.html.erb").to_s
43
+
44
+ return partial_path if @map.key? partial_path
45
+
46
+ # Using the raw RenderParser returns a pathish with the partial _
47
+ # components/_badge
48
+ partial_path = Pathname.new(view_path).join(pathish).join("../#{wrapper.basename}.html.erb").to_s
49
+
50
+ return partial_path if @map.key? partial_path
51
+ end
52
+ end
53
+
54
+ warn("Couldn't resolve pathish: #{pathish}")
55
+
56
+ "UNKNOWN TEMPLATE: #{pathish}"
57
+ end
58
+
59
+ def register(path)
60
+ @map[path] = ActionView::Template.new(
61
+ File.read(path),
62
+ path,
63
+ ActionView::Template::Handlers::ERB,
64
+ locals: nil,
65
+ format: :html,
66
+ )
67
+ end
68
+
69
+ def to_graph
70
+ Graph.new(@map.keys, self)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LooseErbs
4
+ class Scanner
5
+ singleton_class.attr_accessor :parser_class
6
+
7
+ self.parser_class = if defined?(ActionView::RenderParser::Default)
8
+ ActionView::RenderParser::Default
9
+ else
10
+ ActionView::RenderParser
11
+ end
12
+
13
+ def initialize(app = Rails.application)
14
+ @app = app
15
+ end
16
+
17
+ def renders
18
+ parsed_renders = []
19
+
20
+ each_file_in(helper_paths) do |helper_path|
21
+ parsed_renders.concat parsed_renders_in(helper_path)
22
+ end.uniq
23
+
24
+ parsed_renders
25
+ end
26
+
27
+ private
28
+ attr_reader :app
29
+
30
+ def helper_paths
31
+ app.paths["app/helpers"]
32
+ end
33
+
34
+ def each_file_in(paths, &block)
35
+ paths.each do |root_path|
36
+ Dir["#{Rails.root.join(root_path)}/**/*.rb"].each(&block)
37
+ end
38
+ end
39
+
40
+ def parsed_renders_in(file_path)
41
+ parser = self.class.parser_class.new(file_path, File.read(file_path))
42
+ parser.render_calls
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LooseErbs
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.3"
5
5
  end
data/lib/loose_erbs.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "action_view"
4
4
  require "action_view/dependency_tracker"
5
+ require "optparse"
5
6
 
6
7
  require_relative "loose_erbs/version"
7
8
 
@@ -14,70 +15,203 @@ module LooseErbs
14
15
  ActionView::DependencyTracker::RipperTracker
15
16
  end
16
17
 
17
- class Template
18
- class << self
19
- def from(path)
20
- new(path, File.read(path))
21
- end
22
- end
18
+ LooseFilter = ->(node) { node.loose? }
23
19
 
24
- attr_reader :path, :source
20
+ RegexpIncludeFactory = ->(regexp) {
21
+ ->(node) { node.identifier.match?(regexp) }
22
+ }
23
+ RegexpExcludeFactory = ->(regexp) {
24
+ ->(node) { !node.identifier.match?(regexp) }
25
+ }
25
26
 
26
- def initialize(path, source)
27
- @path = path
28
- @source = source
27
+ class FilterChain
28
+ def initialize(filters)
29
+ @filters = filters
29
30
  end
30
31
 
31
- def dependencies
32
- LooseErbs.parser_class.call(path, self)
32
+ def filter(elements)
33
+ @filters.reduce(elements) do |filtered_elements, filter|
34
+ filtered_elements.filter(&filter)
35
+ end
33
36
  end
37
+ end
34
38
 
35
- def handler
36
- ActionView::Template::Handlers::ERB
37
- end
39
+ class Cli
40
+ def initialize(argv)
41
+ @options = {}
38
42
 
39
- def type
40
- ["text/html"]
43
+ option_parser.parse!(into: @options)
41
44
  end
42
45
 
43
- def inspect
44
- path
46
+ def run
47
+ require File.expand_path("./config/environment")
48
+
49
+ nodes = registry.to_graph
50
+
51
+ unless options[:all]
52
+ used_erbs = scanner.renders.map { registry.lookup(_1) }.to_set
53
+
54
+ visitor = Graph::LooseVisitor.new
55
+ nodes.select { |node|
56
+ # assume regular templates are good until controller parsing is added
57
+ used_erbs.include?(node.identifier) || !node.partial?
58
+ }.each { _1.accept(visitor) }
59
+ end
60
+
61
+ nodes = FilterChain.new(filters).filter(nodes) unless filters.empty?
62
+
63
+ if options[:trees]
64
+ nodes.each(&:print_tree)
65
+
66
+ true
67
+ else
68
+ puts "\nLoose ERBs:" unless nodes.empty?
69
+ nodes.each(&:print)
70
+
71
+ nodes.empty?
72
+ end
45
73
  end
74
+
75
+ private
76
+ attr_reader :options
77
+
78
+ def registry
79
+ Registry.new(ActionController::Base.view_paths)
80
+ end
81
+
82
+ def scanner
83
+ Scanner.new
84
+ end
85
+
86
+ def filters
87
+ [
88
+ LooseFilter,
89
+ (RegexpIncludeFactory.call(options[:include]) if options[:include]),
90
+ (RegexpExcludeFactory.call(options[:exclude]) if options[:exclude]),
91
+ ].compact
92
+ end
93
+
94
+ def option_parser
95
+ OptionParser.new do |parser|
96
+ parser.on("--trees", "Print files and their dependencies")
97
+ parser.on("--all", "Print all files with no parents (defaults to only partials)")
98
+ parser.on("--include [REGEXP]", Regexp, "Only print files that match [REGEXP]")
99
+ parser.on("--exclude [REGEXP]", Regexp, "Do not print files that match [REGEXP]")
100
+ end
101
+ end
46
102
  end
47
103
 
48
- class Registry
49
- def initialize
50
- @map = {}
51
- end
104
+ class Graph
105
+ class Printer
106
+ def initialize(root)
107
+ @node_stack = [[root, -1]]
108
+ @seen_nodes = Set.new
109
+ end
52
110
 
53
- def print
54
- @map.values.each do |template|
55
- puts template.inspect
111
+ def print_tree
112
+ while !@node_stack.empty?
113
+ node, depth = @node_stack.pop
56
114
 
57
- template.dependencies.each do |dependency|
58
- puts "└── #{dependency}"
115
+ puts "#{(" " * depth) + "└── " if depth >= 0}#{node.identifier}"
116
+
117
+ if @seen_nodes.include?(node) && !node.children.empty?
118
+ puts (" " * (depth + 1)) + "└── ..."
119
+ else
120
+ @seen_nodes << node
121
+
122
+ node.children.each do |child|
123
+ @node_stack << [child, depth + 1]
124
+ end
125
+ end
59
126
  end
127
+ puts
128
+ end
129
+ end
130
+
131
+ class LooseVisitor
132
+ def visit(node)
133
+ return unless node.loose?
134
+
135
+ node.not_loose!
136
+
137
+ node.children.each { visit(_1) }
60
138
  end
61
139
  end
62
140
 
63
- def register(path)
64
- @map[path] = Template.from(path)
141
+ class Node
142
+ attr_reader :children, :identifier, :parents
143
+
144
+ def initialize(identifier)
145
+ @identifier = identifier
146
+ @children = []
147
+ @parents = []
148
+ @loose = true
149
+ end
150
+
151
+ def accept(visitor)
152
+ visitor.visit(self)
153
+ end
154
+
155
+ def loose?
156
+ @loose
157
+ end
158
+
159
+ def not_loose!
160
+ @loose = false
161
+ end
162
+
163
+ def partial?
164
+ Pathname.new(identifier).basename.to_s.start_with?("_")
165
+ end
166
+
167
+ def print
168
+ puts identifier
169
+ end
170
+
171
+ def print_tree
172
+ Printer.new(self).print_tree
173
+ end
65
174
  end
66
- end
67
175
 
68
- class Cli
69
- def run
70
- registry = Registry.new
176
+ include Enumerable
177
+
178
+ def initialize(keys, registry)
179
+ @node_map = keys.to_h { [_1, Node.new(_1)] }
180
+ @registry = registry
71
181
 
72
- views.each { registry.register(_1) }
182
+ each { process(_1) }
183
+ end
73
184
 
74
- registry.print
185
+ def each(&block)
186
+ nodes.each(&block)
75
187
  end
76
188
 
77
189
  private
190
+ attr_reader :registry
78
191
 
79
- def views
80
- Dir["#{Dir.pwd}/app/views/**/*.html.erb"]
81
- end
192
+ def nodes
193
+ @node_map.values
194
+ end
195
+
196
+ def process(node)
197
+ assign_children!(node)
198
+ assign_parents!(node)
199
+ end
200
+
201
+ def assign_children!(node)
202
+ registry.dependencies_for(node.identifier).each do |identifier|
203
+ # warn("No template registered for path: #{template.identifier}")
204
+ node.children << @node_map.fetch(identifier) { Node.new(identifier) }
205
+ end
206
+ end
207
+
208
+ def assign_parents!(node)
209
+ node.children.each do |child_node|
210
+ child_node.parents << node
211
+ end
212
+ end
82
213
  end
83
214
  end
215
+
216
+ require_relative "loose_erbs/registry"
217
+ require_relative "loose_erbs/scanner"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loose_erbs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hartley McGuire
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-31 00:00:00.000000000 Z
11
+ date: 2024-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -32,11 +32,14 @@ executables:
32
32
  extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
+ - ".rubocop.yml"
35
36
  - LICENSE.txt
36
37
  - README.md
37
38
  - Rakefile
38
39
  - exe/loose_erbs
39
40
  - lib/loose_erbs.rb
41
+ - lib/loose_erbs/registry.rb
42
+ - lib/loose_erbs/scanner.rb
40
43
  - lib/loose_erbs/version.rb
41
44
  homepage: https://github.com/skipkayhil/loose_erbs
42
45
  licenses: