rails-footnotes 3.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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