rails6-footnotes 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +25 -0
- data/.gitignore +15 -0
- data/.rspec.example +1 -0
- data/CHANGELOG +129 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +187 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +188 -0
- data/Rakefile +18 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/gemfiles/Gemfile.rails-3.2.22 +5 -0
- data/gemfiles/Gemfile.rails-4.0.x +6 -0
- data/gemfiles/Gemfile.rails-4.1.x +5 -0
- data/gemfiles/Gemfile.rails-4.2.x +5 -0
- data/gemfiles/Gemfile.rails-edge +5 -0
- data/lib/generators/rails_footnotes/install_generator.rb +14 -0
- data/lib/generators/templates/rails_footnotes.rb +26 -0
- data/lib/rails-footnotes.rb +68 -0
- data/lib/rails-footnotes/abstract_note.rb +178 -0
- data/lib/rails-footnotes/each_with_rescue.rb +36 -0
- data/lib/rails-footnotes/extension.rb +24 -0
- data/lib/rails-footnotes/filter.rb +359 -0
- data/lib/rails-footnotes/notes/all.rb +1 -0
- data/lib/rails-footnotes/notes/assigns_note.rb +60 -0
- data/lib/rails-footnotes/notes/controller_note.rb +55 -0
- data/lib/rails-footnotes/notes/cookies_note.rb +17 -0
- data/lib/rails-footnotes/notes/env_note.rb +24 -0
- data/lib/rails-footnotes/notes/files_note.rb +49 -0
- data/lib/rails-footnotes/notes/filters_note.rb +51 -0
- data/lib/rails-footnotes/notes/javascripts_note.rb +16 -0
- data/lib/rails-footnotes/notes/layout_note.rb +26 -0
- data/lib/rails-footnotes/notes/log_note.rb +47 -0
- data/lib/rails-footnotes/notes/log_note/note_logger.rb +59 -0
- data/lib/rails-footnotes/notes/params_note.rb +21 -0
- data/lib/rails-footnotes/notes/partials_note.rb +38 -0
- data/lib/rails-footnotes/notes/queries_note.rb +121 -0
- data/lib/rails-footnotes/notes/routes_note.rb +60 -0
- data/lib/rails-footnotes/notes/session_note.rb +27 -0
- data/lib/rails-footnotes/notes/stylesheets_note.rb +16 -0
- data/lib/rails-footnotes/notes/view_note.rb +41 -0
- data/lib/rails-footnotes/version.rb +3 -0
- data/lib/rails6-footnotes.rb +1 -0
- data/rails-footnotes.gemspec +22 -0
- data/spec/abstract_note_spec.rb +89 -0
- data/spec/app/assets/config/manifest.js +2 -0
- data/spec/app/assets/javascripts/foobar.js +1 -0
- data/spec/app/assets/stylesheets/foobar.css +0 -0
- data/spec/app/views/files/index.html.erb +1 -0
- data/spec/app/views/layouts/application.html.erb +12 -0
- data/spec/app/views/partials/_foo.html.erb +1 -0
- data/spec/app/views/partials/index.html.erb +1 -0
- data/spec/controllers/files_note_controller_spec.rb +38 -0
- data/spec/controllers/footnotes_controller_spec.rb +128 -0
- data/spec/controllers/log_note_controller_spec.rb +32 -0
- data/spec/controllers/partials_note_controller_spec.rb +28 -0
- data/spec/env_note_spec.rb +73 -0
- data/spec/fixtures/html_download.html +5 -0
- data/spec/footnotes_spec.rb +234 -0
- data/spec/notes/assigns_note_spec.rb +50 -0
- data/spec/notes/controller_note_spec.rb +12 -0
- data/spec/notes/files_note_spec.rb +26 -0
- data/spec/notes/javascripts_note_spec.rb +18 -0
- data/spec/notes/stylesheets_note_spec.rb +19 -0
- data/spec/notes/view_note_spec.rb +12 -0
- data/spec/spec_helper.rb +68 -0
- metadata +153 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module Footnotes
|
2
|
+
module Notes
|
3
|
+
class RoutesNote < AbstractNote
|
4
|
+
def initialize(controller)
|
5
|
+
@controller = controller
|
6
|
+
@parsed_routes = parse_routes
|
7
|
+
end
|
8
|
+
|
9
|
+
def legend
|
10
|
+
"Routes for #{@controller.class.to_s}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def content
|
14
|
+
mount_table(@parsed_routes.unshift([:path, :name, :options, :requirements]), :summary => "Debug information for #{title}")
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
def parse_routes
|
19
|
+
routes_with_name = Rails.application.routes.named_routes.to_a.flatten
|
20
|
+
|
21
|
+
return Rails.application.routes.filtered_routes(:controller => @controller.controller_path).collect do |route|
|
22
|
+
# Catch routes name if exists
|
23
|
+
i = routes_with_name.index(route)
|
24
|
+
name = i ? routes_with_name[i-1].to_s : ''
|
25
|
+
|
26
|
+
# Catch segments requirements
|
27
|
+
req = route.conditions
|
28
|
+
[escape(name), route.conditions.keys.join, route.requirements.reject{|key,value| key == :controller}.inspect, req.inspect]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Extensions
|
35
|
+
module Routes
|
36
|
+
|
37
|
+
def __hash_diff(hash_self, other)
|
38
|
+
# ActiveSupport::Deprecation.warn "Hash#diff is no longer used inside of Rails,
|
39
|
+
# and is being deprecated with no replacement. If you're using it to compare hashes for the purpose of testing, please use MiniTest's diff instead."
|
40
|
+
hash_self.dup.delete_if { |k, v| other[k] == v }.merge!(other.dup.delete_if { |k, v| hash_self.has_key?(k) })
|
41
|
+
end
|
42
|
+
|
43
|
+
# Filter routes according to the filter sent
|
44
|
+
#
|
45
|
+
def filtered_routes(filter = {})
|
46
|
+
return [] unless filter.is_a?(Hash)
|
47
|
+
return routes.reject do |r|
|
48
|
+
filter_diff = __hash_diff(filter, r.requirements)
|
49
|
+
route_diff = __hash_diff(r.requirements, filter)
|
50
|
+
|
51
|
+
(filter_diff == filter) || (filter_diff != route_diff)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if Footnotes::Notes::RoutesNote.included?
|
59
|
+
ActionDispatch::Routing::RouteSet.send :include, Footnotes::Extensions::Routes
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Footnotes
|
2
|
+
module Notes
|
3
|
+
class SessionNote < AbstractNote
|
4
|
+
def initialize(controller)
|
5
|
+
session = controller.session
|
6
|
+
if session
|
7
|
+
if session.respond_to? :to_hash
|
8
|
+
# rails >= 2.3
|
9
|
+
session = session.to_hash
|
10
|
+
else
|
11
|
+
#rails < 2.3
|
12
|
+
session = session.data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@session = (session || {}).symbolize_keys
|
16
|
+
end
|
17
|
+
|
18
|
+
def title
|
19
|
+
"Session (#{@session.length})"
|
20
|
+
end
|
21
|
+
|
22
|
+
def content
|
23
|
+
mount_table_for_hash(@session, :summary => "Debug information for #{title}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails-footnotes/notes/files_note"
|
2
|
+
|
3
|
+
module Footnotes
|
4
|
+
module Notes
|
5
|
+
class StylesheetsNote < FilesNote
|
6
|
+
def title
|
7
|
+
"Stylesheets (#{@files.length})"
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
def scan_text(text)
|
12
|
+
text.scan(/<link[^>]+href\s*=\s*['"]([^>?'"]+\.css)/im).flatten
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Footnotes
|
2
|
+
module Notes
|
3
|
+
class ViewNote < AbstractNote
|
4
|
+
cattr_accessor :template
|
5
|
+
|
6
|
+
def self.start!(controller)
|
7
|
+
@subscriber ||= ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
|
8
|
+
event = ActiveSupport::Notifications::Event.new *args
|
9
|
+
self.template = {:file => event.payload[:identifier], :duration => event.duration}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(controller)
|
14
|
+
@controller = controller
|
15
|
+
end
|
16
|
+
|
17
|
+
def row
|
18
|
+
:edit
|
19
|
+
end
|
20
|
+
|
21
|
+
def title
|
22
|
+
"View (#{"%.3f" % self.template[:duration]}ms)"
|
23
|
+
end
|
24
|
+
|
25
|
+
def link
|
26
|
+
escape(Footnotes::Filter.prefix(filename, 1, 1))
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid?
|
30
|
+
prefix? && filename && File.exists?(filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def filename
|
36
|
+
@filename ||= self.class.template[:file]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "./rails-footnotes"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rails-footnotes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rails6-footnotes"
|
7
|
+
s.version = Footnotes::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Roman V. Babenko", "José Valim", "Keenan Brock", "Duane Johnson", "Adrien Siami", "André Arko"]
|
10
|
+
s.email = ["andre@arko.net"]
|
11
|
+
s.homepage = "http://github.com/indirect/rails-footnotes"
|
12
|
+
s.summary = %q{Every Rails page has footnotes that gives information about your application and links back to your editor.}
|
13
|
+
s.description = %q{Every Rails page has footnotes that gives information about your application and links back to your editor.}
|
14
|
+
|
15
|
+
s.add_dependency "rails", "~> 6.0"
|
16
|
+
s.required_ruby_version = "~> 3.0"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Footnotes::Notes::AbstractNote do
|
4
|
+
before do
|
5
|
+
@note = Footnotes::Notes::AbstractNote.new
|
6
|
+
@notes = Footnotes::Filter.notes
|
7
|
+
Footnotes::Filter.notes = [:abstract]
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Footnotes::Filter.notes = @notes
|
12
|
+
end
|
13
|
+
|
14
|
+
it {expect(described_class).to respond_to :start!}
|
15
|
+
it {expect(described_class).to respond_to :close!}
|
16
|
+
it {expect(described_class).to respond_to :title}
|
17
|
+
|
18
|
+
it {should respond_to :to_sym}
|
19
|
+
|
20
|
+
describe '#to_sym' do
|
21
|
+
subject { super().to_sym }
|
22
|
+
it {should eql :abstract}
|
23
|
+
end
|
24
|
+
|
25
|
+
it { expect(described_class).to be_included }
|
26
|
+
specify do
|
27
|
+
Footnotes::Filter.notes = []
|
28
|
+
expect(described_class).not_to be_included
|
29
|
+
end
|
30
|
+
|
31
|
+
it { should respond_to :row }
|
32
|
+
it { should respond_to :legend }
|
33
|
+
it { should respond_to :link }
|
34
|
+
it { should respond_to :onclick }
|
35
|
+
it { should respond_to :stylesheet }
|
36
|
+
it { should respond_to :javascript }
|
37
|
+
|
38
|
+
it { should respond_to :valid? }
|
39
|
+
it { should be_valid }
|
40
|
+
|
41
|
+
it { should respond_to :has_fieldset? }
|
42
|
+
it { should_not have_fieldset }
|
43
|
+
|
44
|
+
specify { Footnotes::Filter.prefix = ''; should_not be_prefix }
|
45
|
+
specify do
|
46
|
+
Footnotes::Filter.prefix = 'txmt://open?url=file://%s&line=%d&column=%d'
|
47
|
+
should be_prefix
|
48
|
+
end
|
49
|
+
|
50
|
+
#TODO should be moved to builder
|
51
|
+
#helpers
|
52
|
+
specify { expect(subject.escape('<')).to eql '<' }
|
53
|
+
specify { expect(subject.escape('&')).to eql '&' }
|
54
|
+
specify { expect(subject.escape('>')).to eql '>' }
|
55
|
+
|
56
|
+
specify { expect(subject.mount_table([])).to be_blank }
|
57
|
+
specify { expect(subject.mount_table([['h1', 'h2', 'h3']], :class => 'table')).to be_blank }
|
58
|
+
|
59
|
+
specify {
|
60
|
+
tab = <<-TABLE
|
61
|
+
<table class="table" >
|
62
|
+
<thead><tr><th>H1</th></tr></thead>
|
63
|
+
<tbody><tr><td>r1c1</td></tr></tbody>
|
64
|
+
</table>
|
65
|
+
TABLE
|
66
|
+
|
67
|
+
expect(subject.mount_table([['h1'],['r1c1']], :class => 'table')).to eql tab
|
68
|
+
}
|
69
|
+
|
70
|
+
specify {
|
71
|
+
tab = <<-TABLE
|
72
|
+
<table >
|
73
|
+
<thead><tr><th>H1</th><th>H2</th><th>H3</th></tr></thead>
|
74
|
+
<tbody><tr><td>r1c1</td><td>r1c2</td><td>r1c3</td></tr></tbody>
|
75
|
+
</table>
|
76
|
+
TABLE
|
77
|
+
expect(subject.mount_table([['h1', 'h2', 'h3'],['r1c1', 'r1c2', 'r1c3']])).to eql tab
|
78
|
+
}
|
79
|
+
|
80
|
+
specify {
|
81
|
+
tab = <<-TABLE
|
82
|
+
<table >
|
83
|
+
<thead><tr><th>H1</th><th>H2</th><th>H3</th></tr></thead>
|
84
|
+
<tbody><tr><td>r1c1</td><td>r1c2</td><td>r1c3</td></tr><tr><td>r2c1</td><td>r2c2</td><td>r2c3</td></tr></tbody>
|
85
|
+
</table>
|
86
|
+
TABLE
|
87
|
+
subject.mount_table([['h1', 'h2', 'h3'], ['r1c1', 'r1c2', 'r1c3'], ['r2c1', 'r2c2', 'r2c3']])
|
88
|
+
}
|
89
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
// Foobar
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
FILES INDEX
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<%= javascript_include_tag 'foobar' %>
|
5
|
+
<%= javascript_include_tag 'http://google.com/whatever.js' %>
|
6
|
+
<%= stylesheet_link_tag 'foobar' %>
|
7
|
+
<%= stylesheet_link_tag 'http://google.com/whatever.css' %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<%= yield %>
|
11
|
+
</body>
|
12
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
bar
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render 'foo' %>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class FilesController < ApplicationController
|
4
|
+
def index
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe FilesController, type: :controller do
|
9
|
+
render_views
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
Footnotes.enabled = true
|
13
|
+
end
|
14
|
+
|
15
|
+
after :all do
|
16
|
+
Footnotes.enabled = false
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'includes stylesheets assets in the response' do
|
20
|
+
get :index
|
21
|
+
expect(response.body).to include("FILES INDEX")
|
22
|
+
js_debug = first('fieldset#javascripts_debug_info div', visible: false)
|
23
|
+
expect(js_debug).to have_selector('li a', visible: false, count: 1)
|
24
|
+
expect(js_debug).to have_selector('li a', text: /foobar\.js/, visible: false)
|
25
|
+
link = js_debug.first('a', visible: false)
|
26
|
+
expect(link['href']).to eq("txmt://open?url=file://#{Rails.root.join('app', 'assets', 'javascripts', 'foobar.js')}&line=1&column=1")
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'includes css assets in the response' do
|
30
|
+
get :index
|
31
|
+
css_debug = first('fieldset#stylesheets_debug_info div', visible: false)
|
32
|
+
expect(css_debug).to have_selector('li a', visible: false, count: 1)
|
33
|
+
expect(css_debug).to have_selector('li a', text: /foobar\.css/, visible: false)
|
34
|
+
link = css_debug.first('a', visible: false)
|
35
|
+
expect(link['href']).to eq("txmt://open?url=file://#{Rails.root.join('app', 'assets', 'stylesheets', 'foobar.css')}&line=1&column=1")
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class FootnotesController < ActionController::Base
|
4
|
+
|
5
|
+
def foo
|
6
|
+
render inline: HTML_DOCUMENT, content_type: 'text/html'
|
7
|
+
end
|
8
|
+
|
9
|
+
def foo_holder
|
10
|
+
render inline: '<html><body><div id="footnotes_holder"></div></body></html>'
|
11
|
+
end
|
12
|
+
|
13
|
+
def foo_js
|
14
|
+
render inline: '<script></script>', content_type: 'text/javascript'
|
15
|
+
end
|
16
|
+
|
17
|
+
def foo_download
|
18
|
+
send_file Rails.root.join('fixtures', 'html_download.html'), :disposition => 'attachment'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe FootnotesController, type: :controller do
|
24
|
+
|
25
|
+
shared_examples 'has_footnotes' do
|
26
|
+
it 'includes footnotes' do
|
27
|
+
get :foo
|
28
|
+
expect(response.body).to have_selector('#footnotes_debug')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples 'has_no_footnotes' do
|
33
|
+
it 'does not include footnotes' do
|
34
|
+
expect(response.body).not_to have_selector('#footnotes_debug')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'does not alter the page by default' do
|
39
|
+
get :foo
|
40
|
+
expect(response.body).to eq(HTML_DOCUMENT)
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with footnotes' do
|
44
|
+
|
45
|
+
before :all do
|
46
|
+
Footnotes.enabled = true
|
47
|
+
end
|
48
|
+
|
49
|
+
after :all do
|
50
|
+
Footnotes.enabled = false
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'by default' do
|
54
|
+
include_context 'has_footnotes'
|
55
|
+
|
56
|
+
before do
|
57
|
+
get :foo
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'includes footnotes in the last div in body' do
|
61
|
+
expect(all('body > :last-child')[0][:id]).to eq('footnotes_debug')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'includes footnotes in the footnoted_holder div if present' do
|
65
|
+
get :foo_holder
|
66
|
+
expect(response.body).to have_selector('#footnotes_holder > #footnotes_debug')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does not alter a html file download' do
|
70
|
+
get :foo_download
|
71
|
+
expect(response.body).to eq(File.open(Rails.root.join('fixtures', 'html_download.html')).read)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'when request is xhr' do
|
76
|
+
include_context 'has_no_footnotes'
|
77
|
+
before do
|
78
|
+
get :foo, xhr: true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'when content type is javascript' do
|
83
|
+
include_context 'has_no_footnotes'
|
84
|
+
before do
|
85
|
+
get :foo_js
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'when footnotes is disabled' do
|
90
|
+
include_context 'has_no_footnotes'
|
91
|
+
before do
|
92
|
+
Footnotes.enabled = false
|
93
|
+
get :foo
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe 'with a proc' do
|
98
|
+
|
99
|
+
it 'yields the controller' do
|
100
|
+
c = nil
|
101
|
+
Footnotes.enabled = lambda { |controller| c = controller}
|
102
|
+
get :foo
|
103
|
+
expect(c).to be_kind_of(ActionController::Base)
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'returning true' do
|
107
|
+
include_context 'has_footnotes'
|
108
|
+
|
109
|
+
before do
|
110
|
+
Footnotes.enabled = lambda { true }
|
111
|
+
get :foo
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'returning false' do
|
116
|
+
include_context 'has_no_footnotes'
|
117
|
+
|
118
|
+
before do
|
119
|
+
Footnotes.enabled = lambda { false }
|
120
|
+
get :foo
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|