rails-footnotes 3.6.3

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.
@@ -0,0 +1,16 @@
1
+ require "#{File.dirname(__FILE__)}/files_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class JavascriptsNote < FilesNote
6
+ def title
7
+ "Javascripts (#{@files.length})"
8
+ end
9
+
10
+ protected
11
+ def scan_text(text)
12
+ text.scan(/<script[^>]+src\s*=\s*['"]([^>?'"]+\.js)/im).flatten
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class LayoutNote < AbstractNote
6
+ def initialize(controller)
7
+ @controller = controller
8
+ end
9
+
10
+ def row
11
+ :edit
12
+ end
13
+
14
+ def link
15
+ escape(Footnotes::Filter.prefix(filename, 1, 1))
16
+ end
17
+
18
+ def valid?
19
+ prefix? && @controller.active_layout
20
+ end
21
+
22
+ protected
23
+ def filename
24
+ File.join(File.expand_path(RAILS_ROOT), 'app', 'layouts', "#{@controller.active_layout.to_s.underscore}").sub('/layouts/layouts/', '/views/layouts/')
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class LogNote < AbstractNote
6
+ def initialize(controller)
7
+ @controller = controller
8
+ end
9
+
10
+ def content
11
+ escape(log_tail).gsub("\n","<br />")
12
+ end
13
+
14
+ protected
15
+ def log_tail
16
+ filename = if RAILS_DEFAULT_LOGGER.instance_variable_get('@log')
17
+ RAILS_DEFAULT_LOGGER.instance_variable_get('@log').path
18
+ else
19
+ RAILS_DEFAULT_LOGGER.instance_variable_get('@logdev').filename
20
+ end
21
+ file_string = File.open(filename).read.to_s
22
+
23
+ # We try to select the specified action from the log
24
+ # If we can't find it, we get the last 100 lines
25
+ #
26
+ if rindex = file_string.rindex('Processing '+@controller.controller_class_name+'#'+@controller.action_name)
27
+ file_string[rindex..-1].gsub(/\e\[.+?m/, '')
28
+ else
29
+ lines = file_string.split("\n")
30
+ index = [lines.size-100,0].max
31
+ lines[index..-1].join("\n")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class ParamsNote < AbstractNote
6
+ def initialize(controller)
7
+ @params = controller.params.symbolize_keys
8
+ end
9
+
10
+ def title
11
+ "Params (#{@params.length})"
12
+ end
13
+
14
+ def content
15
+ mount_table_for_hash(@params)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,172 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class QueriesNote < AbstractNote
6
+ @@alert_db_time = 0.16
7
+ @@alert_sql_number = 8
8
+ @@sql = []
9
+ cattr_accessor :sql, :alert_db_time, :alert_sql_number, :alert_explain, :instance_writter => false
10
+
11
+ def self.start!(controller)
12
+ @@sql = []
13
+ end
14
+
15
+ def self.to_sym
16
+ :queries
17
+ end
18
+
19
+ def title
20
+ db_time = @@sql.inject(0){|sum, item| sum += item.time }
21
+ query_color = generate_red_color(@@sql.length, alert_sql_number)
22
+ db_color = generate_red_color(db_time, alert_db_time)
23
+
24
+ <<-TITLE
25
+ <span style="background-color:#{query_color}">Queries (#{@@sql.length})</span>
26
+ <span style="background-color:#{db_color}">DB (#{"%.6f" % db_time}s)</span>
27
+ TITLE
28
+ end
29
+
30
+ def stylesheet
31
+ <<-STYLESHEET
32
+ #queries_debug_info table td, #queries_debug_info table th{border:1px solid #A00; padding:0 3px; text-align:center;}
33
+ #queries_debug_info table thead, #queries_debug_info table tbody {color:#A00;}
34
+ #queries_debug_info p {background-color:#F3F3FF; border:1px solid #CCC; margin:12px; padding:4px 6px;}
35
+ #queries_debug_info a:hover {text-decoration:underline;}
36
+ STYLESHEET
37
+ end
38
+
39
+ def content
40
+ html = ''
41
+
42
+ @@sql.each_with_index do |item, i|
43
+ sql_links = []
44
+ sql_links << "<a href=\"javascript:Footnotes.toggle('qtable_#{i}')\" style=\"color:#A00;\">explain</a>" if item.explain
45
+ sql_links << "<a href=\"javascript:Footnotes.toggle('qtrace_#{i}')\" style=\"color:#00A;\">trace</a>" if item.trace
46
+
47
+ html << <<-HTML
48
+ <b id="qtitle_#{i}">#{escape(item.type.to_s.upcase)}</b> (#{sql_links.join(' | ')})<br />
49
+ #{print_name_and_time(item.name, item.time)}<br />
50
+ <span id="explain_#{i}">#{print_query(item.query)}</span><br />
51
+ #{print_explain(i, item.explain) if item.explain}
52
+ <p id="qtrace_#{i}" style="display:none;">#{parse_trace(item.trace) if item.trace}</p><br />
53
+ HTML
54
+ end
55
+
56
+ return html
57
+ end
58
+
59
+ protected
60
+ def parse_explain(results)
61
+ table = []
62
+ table << results.fetch_fields.map(&:name)
63
+ results.each do |row|
64
+ table << row
65
+ end
66
+ table
67
+ end
68
+
69
+ def parse_trace(trace)
70
+ trace.map do |t|
71
+ s = t.split(':')
72
+ %[<a href="#{escape(Footnotes::Filter.prefix("#{RAILS_ROOT}/#{s[0]}", s[1].to_i, 1))}">#{escape(t)}</a><br />]
73
+ end.join
74
+ end
75
+
76
+ def print_name_and_time(name, time)
77
+ "<span style='background-color:#{generate_red_color(time, alert_ratio)}'>#{escape(name || 'SQL')} (#{sprintf('%f', time)}s)</span>"
78
+ end
79
+
80
+ def print_query(query)
81
+ escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
82
+ end
83
+
84
+ def print_explain(i, explain)
85
+ mount_table(parse_explain(explain), :id => "qtable_#{i}", :style => 'margin:10px;display:none;')
86
+ end
87
+
88
+ def generate_red_color(value, alert)
89
+ c = ((value.to_f/alert).to_i - 1) * 16
90
+ c = 0 if c < 0
91
+ c = 15 if c > 15
92
+
93
+ c = (15-c).to_s(16)
94
+ "#ff#{c*4}"
95
+ end
96
+
97
+ def alert_ratio
98
+ alert_db_time / alert_sql_number
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+ module Extensions
105
+ class Sql
106
+ attr_accessor :type, :name, :time, :query, :explain, :trace
107
+
108
+ def initialize(type, name, time, query, explain)
109
+ @type = type
110
+ @name = name
111
+ @time = time
112
+ @query = query
113
+ @explain = explain
114
+
115
+ # Strip, select those ones from app and reject first two, because they
116
+ # are from the plugin
117
+ @trace = Kernel.caller.collect(&:strip).select{|i| i.gsub!(/^#{RAILS_ROOT}\//im, '') }[2..-1]
118
+ end
119
+ end
120
+
121
+ module QueryAnalyzer
122
+ def self.included(base)
123
+ base.class_eval do
124
+ alias_method_chain :execute, :analyzer
125
+ end
126
+ end
127
+
128
+ def execute_with_analyzer(query, name = nil)
129
+ query_results = nil
130
+ time = Benchmark.realtime { query_results = execute_without_analyzer(query, name) }
131
+
132
+ if query =~ /^(select|create|update|delete)\b/i
133
+ type = $&.downcase.to_sym
134
+ explain = nil
135
+
136
+ if adapter_name == 'MySQL' && type == :select
137
+ log_silence do
138
+ explain = execute_without_analyzer("EXPLAIN #{query}", name)
139
+ end
140
+ end
141
+ Footnotes::Notes::QueriesNote.sql << Footnotes::Extensions::Sql.new(type, name, time, query, explain)
142
+ end
143
+
144
+ query_results
145
+ end
146
+ end
147
+
148
+ module AbstractAdapter
149
+ def log_silence
150
+ result = nil
151
+ if @logger
152
+ @logger.silence do
153
+ result = yield
154
+ end
155
+ else
156
+ result = yield
157
+ end
158
+ result
159
+ end
160
+ end
161
+
162
+ end
163
+ end
164
+
165
+ if Footnotes::Notes::QueriesNote.included?
166
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Footnotes::Extensions::AbstractAdapter
167
+ ActiveRecord::ConnectionAdapters.local_constants.each do |adapter|
168
+ next unless adapter =~ /.*[^Abstract]Adapter$/
169
+ next if adapter =~ /SQLiteAdapter$/
170
+ eval("ActiveRecord::ConnectionAdapters::#{adapter}").send :include, Footnotes::Extensions::QueryAnalyzer
171
+ end
172
+ end
@@ -0,0 +1,59 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class RoutesNote < AbstractNote
6
+ def initialize(controller)
7
+ @controller = controller
8
+ @parsed_routes = parse_routes
9
+ end
10
+
11
+ def legend
12
+ "Routes for #{@controller.class.to_s}"
13
+ end
14
+
15
+ def content
16
+ mount_table(@parsed_routes.unshift([:path, :name, :options, :requirements]))
17
+ end
18
+
19
+ protected
20
+ def parse_routes
21
+ routes_with_name = ActionController::Routing::Routes.named_routes.to_a.flatten
22
+
23
+ return ActionController::Routing::Routes.filtered_routes(:controller => @controller.controller_path).collect do |route|
24
+ # Catch routes name if exists
25
+ i = routes_with_name.index(route)
26
+ name = i ? routes_with_name[i-1].to_s : ''
27
+
28
+ # Catch segments requirements
29
+ req = {}
30
+ route.segments.each do |segment|
31
+ next unless segment.is_a?(ActionController::Routing::DynamicSegment) && segment.regexp
32
+ req[segment.key.to_sym] = segment.regexp
33
+ end
34
+
35
+ [escape(name), route.segments.join, route.requirements.reject{|key,value| key == :controller}.inspect, req.inspect]
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ module Extensions
42
+ module Routes
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 = filter.diff(r.requirements)
49
+ route_diff = r.requirements.diff(filter)
50
+ (filter_diff == filter) || (filter_diff != route_diff)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ if Footnotes::Notes::RoutesNote.included?
58
+ ActionController::Routing::RouteSet.send :include, Footnotes::Extensions::Routes
59
+ end
@@ -0,0 +1,24 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class RpmNote < AbstractNote
6
+ def initialize(controller)
7
+ @rpm_id=NewRelic::Agent.instance.transaction_sampler.current_sample_id
8
+ end
9
+
10
+ def row
11
+ :edit
12
+ end
13
+
14
+ def link
15
+ #{:controller => 'newrelic', :action => 'show_sample_detail', :id => @rpm_id}
16
+ "/newrelic/show_sample_detail/#{@rpm_id}"
17
+ end
18
+
19
+ def valid?
20
+ !NewRelic::Config.instance['skip_developer_route']
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class SessionNote < AbstractNote
6
+ def initialize(controller)
7
+ @session = (controller.session || {}).symbolize_keys
8
+ end
9
+
10
+ def title
11
+ "Session (#{@session.length})"
12
+ end
13
+
14
+ def content
15
+ mount_table_for_hash(@session)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ require "#{File.dirname(__FILE__)}/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,35 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class ViewNote < AbstractNote
6
+ def initialize(controller)
7
+ @controller = controller
8
+ @template = controller.instance_variable_get(:@template)
9
+ end
10
+
11
+ def row
12
+ :edit
13
+ end
14
+
15
+ def link
16
+ escape(Footnotes::Filter.prefix(filename, 1, 1))
17
+ end
18
+
19
+ def valid?
20
+ prefix? && first_render?
21
+ end
22
+
23
+ protected
24
+
25
+ def first_render?
26
+ @template.instance_variable_get(:@_first_render)
27
+ end
28
+
29
+ def filename
30
+ @filename ||= @template.instance_variable_get(:@_first_render).filename
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,199 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ require 'action_controller'
4
+ require 'action_controller/test_case'
5
+ require 'action_controller/test_process'
6
+
7
+ class FootnotesController < ActionController::Base; attr_accessor :template, :performed_render; end
8
+
9
+ module Footnotes::Notes
10
+ class TestNote < AbstractNote
11
+ def self.to_sym; :test; end
12
+ def valid?; true; end
13
+ end
14
+ end
15
+
16
+ class FootnotesTest < Test::Unit::TestCase
17
+ def setup
18
+ @controller = FootnotesController.new
19
+ @controller.request = ActionController::TestRequest.new
20
+ @controller.response = ActionController::TestResponse.new
21
+ @controller.response.body = $html.dup
22
+
23
+ Footnotes::Filter.notes = [ :test ]
24
+ Footnotes::Filter.multiple_notes = false
25
+ @footnotes = Footnotes::Filter.new(@controller)
26
+ end
27
+
28
+ def test_footnotes_controller
29
+ index = @controller.response.body.index(/This is the HTML page/)
30
+ assert_equal 334, index
31
+ end
32
+
33
+ def test_foonotes_included
34
+ footnotes_perform!
35
+ assert_not_equal $html, @controller.response.body
36
+ end
37
+
38
+ def test_footnotes_not_included_when_request_is_xhr
39
+ @controller.request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
40
+ @controller.request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
41
+
42
+ footnotes_perform!
43
+ assert_equal $html, @controller.response.body
44
+ end
45
+
46
+ def test_footnotes_not_included_when_content_type_is_javascript
47
+ @controller.response.headers['Content-Type'] = 'text/javascript'
48
+
49
+ footnotes_perform!
50
+ assert_equal $html, @controller.response.body
51
+ end
52
+
53
+ def test_footnotes_included_when_content_type_is_html
54
+ @controller.response.headers['Content-Type'] = 'text/html'
55
+
56
+ footnotes_perform!
57
+ assert_not_equal $html, @controller.response.body
58
+ end
59
+
60
+ def test_footnotes_included_when_content_type_is_nil
61
+ footnotes_perform!
62
+ assert_not_equal $html, @controller.response.body
63
+ end
64
+
65
+ def test_not_included_when_body_is_not_a_string
66
+ @controller.response.body = Proc.new{ Time.now }
67
+ assert_nothing_raised do
68
+ footnotes_perform!
69
+ end
70
+ end
71
+
72
+ def test_footnotes_prefix
73
+ assert_equal 'txmt://open?url=file://%s&amp;line=%d&amp;column=%d', Footnotes::Filter.prefix
74
+ assert_equal 'txmt://open?url=file://file&amp;line=0&amp;column=0', Footnotes::Filter.prefix('file', 0, 0)
75
+ assert_equal 'txmt://open?url=file://file&amp;line=10&amp;column=10', Footnotes::Filter.prefix('file', 10, 10)
76
+ assert_equal 'txmt://open?url=file://file&amp;line=10&amp;column=10', Footnotes::Filter.prefix('file', 10, 10, 10)
77
+ assert_equal 'txmt://open?url=file://file&amp;line=10&amp;column=10', Footnotes::Filter.prefix('file', '10', '10')
78
+ end
79
+
80
+ def test_notes_are_initialized
81
+ footnotes_perform!
82
+ test_note = @footnotes.instance_variable_get('@notes').first
83
+ assert 'Footnotes::Notes::TestNote', test_note.class
84
+ assert :test, test_note.to_sym
85
+ end
86
+
87
+ def test_notes_links
88
+ note = Footnotes::Notes::TestNote.new
89
+ note.expects(:row).times(2)
90
+ @footnotes.instance_variable_set(:@notes, [note])
91
+ footnotes_perform!
92
+ end
93
+
94
+ def test_notes_fieldset
95
+ note = Footnotes::Notes::TestNote.new
96
+ note.expects(:has_fieldset?).times(3)
97
+ @footnotes.instance_variable_set(:@notes, [note])
98
+ footnotes_perform!
99
+ end
100
+
101
+ def test_multiple_notes
102
+ Footnotes::Filter.multiple_notes = true
103
+ note = Footnotes::Notes::TestNote.new
104
+ note.expects(:has_fieldset?).times(2)
105
+ @footnotes.instance_variable_set(:@notes, [note])
106
+ footnotes_perform!
107
+ end
108
+
109
+ def test_notes_are_reset
110
+ note = Footnotes::Notes::TestNote.new
111
+ note.class.expects(:close!)
112
+ @footnotes.instance_variable_set(:@notes, [note])
113
+ @footnotes.send(:close!, @controller)
114
+ end
115
+
116
+ def test_links_helper
117
+ note = Footnotes::Notes::TestNote.new
118
+ assert_equal '<a href="#" onclick="">Test</a>', @footnotes.send(:link_helper, note)
119
+
120
+ note.expects(:link).times(1).returns(:link)
121
+ assert_equal '<a href="link" onclick="">Test</a>', @footnotes.send(:link_helper, note)
122
+ end
123
+
124
+ def test_links_helper_has_fieldset?
125
+ note = Footnotes::Notes::TestNote.new
126
+ note.expects(:has_fieldset?).times(1).returns(true)
127
+ assert_equal '<a href="#" onclick="Footnotes.hideAllAndToggle(\'test_debug_info\');return false;">Test</a>', @footnotes.send(:link_helper, note)
128
+ end
129
+
130
+ def test_links_helper_onclick
131
+ note = Footnotes::Notes::TestNote.new
132
+ note.expects(:onclick).times(2).returns(:onclick)
133
+ assert_equal '<a href="#" onclick="onclick">Test</a>', @footnotes.send(:link_helper, note)
134
+
135
+ note.expects(:has_fieldset?).times(1).returns(true)
136
+ assert_equal '<a href="#" onclick="onclick">Test</a>', @footnotes.send(:link_helper, note)
137
+ end
138
+
139
+ def test_insert_style
140
+ @controller.response.body = "<head></head><split><body></body>"
141
+ @footnotes = Footnotes::Filter.new(@controller)
142
+ footnotes_perform!
143
+ assert @controller.response.body.split('<split>').first.include?('<!-- Footnotes Style -->')
144
+ end
145
+
146
+ def test_insert_footnotes_inside_body
147
+ @controller.response.body = "<head></head><split><body></body>"
148
+ @footnotes = Footnotes::Filter.new(@controller)
149
+ footnotes_perform!
150
+ assert @controller.response.body.split('<split>').last.include?('<!-- End Footnotes -->')
151
+ end
152
+
153
+ def test_insert_footnotes_inside_holder
154
+ @controller.response.body = "<head></head><split><div id='footnotes_holder'></div>"
155
+ @footnotes = Footnotes::Filter.new(@controller)
156
+ footnotes_perform!
157
+ assert @controller.response.body.split('<split>').last.include?('<!-- End Footnotes -->')
158
+ end
159
+
160
+ def test_insert_text
161
+ @footnotes.send(:insert_text, :after, /<head>/, "Graffiti")
162
+ after = " <head>Graffiti\n"
163
+ assert_equal after, @controller.response.body.to_a[2]
164
+
165
+ @footnotes.send(:insert_text, :before, /<\/body>/, "Notes")
166
+ after = " Notes</body>\n"
167
+ assert_equal after, @controller.response.body.to_a[12]
168
+ end
169
+
170
+ protected
171
+ # First we make sure that footnotes will perform (long life to mocha!)
172
+ # Then we call add_footnotes!
173
+ #
174
+ def footnotes_perform!
175
+ @controller.template.expects(:instance_variable_get).returns(true)
176
+ @controller.template.expects(:template_format).returns('html')
177
+ @controller.performed_render = true
178
+
179
+ Footnotes::Filter.start!(@controller)
180
+ @footnotes.add_footnotes!
181
+ end
182
+ end
183
+
184
+ $html = <<HTML
185
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
186
+ <html>
187
+ <head>
188
+ <title>HTML to XHTML Example: HTML page</title>
189
+ <link rel="Stylesheet" href="htmltohxhtml.css" type="text/css" media="screen">
190
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
191
+ </head>
192
+ <body>
193
+ <p>This is the HTML page. It works and is encoded just like any HTML page you
194
+ have previously done. View <a href="htmltoxhtml2.htm">the XHTML version</a> of
195
+ this page to view the difference between HTML and XHTML.</p>
196
+ <p>You will be glad to know that no changes need to be made to any of your CSS files.</p>
197
+ </body>
198
+ </html>
199
+ HTML