rails3-footnotes 4.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,179 @@
1
+ module Footnotes
2
+ module Notes
3
+ # This is the abstract class for notes.
4
+ # You can overwrite all instance public methods to create your notes.
5
+ #
6
+ class AbstractNote
7
+
8
+ # Class methods. Do NOT overwrite them.
9
+ #
10
+ class << self
11
+ # Returns the symbol that represents this note.
12
+ # It's the name of the class, underscored and without _note.
13
+ #
14
+ # For example, for ControllerNote it will return :controller.
15
+ #
16
+ def to_sym
17
+ @note_sym ||= self.title.underscore.to_sym
18
+ end
19
+
20
+ # Returns the title that represents this note.
21
+ # It's the name of the class without Note.
22
+ #
23
+ # For example, for ControllerNote it will return Controller.
24
+ #
25
+ def title
26
+ @note_title ||= self.name.match(/^Footnotes::Notes::(\w+)Note$/)[1]
27
+ end
28
+
29
+ # Return true if Note is included in notes array.
30
+ #
31
+ def included?
32
+ Footnotes::Filter.notes.include?(self.to_sym)
33
+ end
34
+
35
+ # Action to be called to start the Note.
36
+ # This is applied as a before_filter.
37
+ #
38
+ def start!(controller = nil)
39
+ end
40
+
41
+ # Action to be called after the Note was used.
42
+ # This is applied as an after_filter.
43
+ #
44
+ def close!(controller = nil)
45
+ end
46
+ end
47
+
48
+ # Initialize notes.
49
+ # Always receives a controller.
50
+ #
51
+ def initialize(controller = nil)
52
+ end
53
+
54
+ # Returns the symbol that represents this note.
55
+ #
56
+ def to_sym
57
+ self.class.to_sym
58
+ end
59
+
60
+ # Specifies in which row should appear the title.
61
+ # The default is :show.
62
+ #
63
+ def row
64
+ :show
65
+ end
66
+
67
+ # Returns the title to be used as link.
68
+ # The default is the note title.
69
+ #
70
+ def title
71
+ self.class.title
72
+ end
73
+
74
+ # If has_fieldset? is true, create a fieldset with the value returned as legend.
75
+ # By default, returns the title of the class (defined above).
76
+ #
77
+ def legend
78
+ self.class.title
79
+ end
80
+
81
+ # If content is defined, has_fieldset? returns true and the value of content
82
+ # is displayed when the Note is clicked. See has_fieldset? below for more info.
83
+ #
84
+ # def content
85
+ # end
86
+
87
+ # Set href field for Footnotes links.
88
+ # If it's nil, Footnotes will use '#'.
89
+ #
90
+ def link
91
+ end
92
+
93
+ # Set onclick field for Footnotes links.
94
+ # If it's nil, Footnotes will make it open the fieldset.
95
+ #
96
+ def onclick
97
+ end
98
+
99
+ # Insert here any additional stylesheet.
100
+ # This is directly inserted into a <style> tag.
101
+ #
102
+ def stylesheet
103
+ end
104
+
105
+ # Insert here any additional javascript.
106
+ # This is directly inserted into a <script> tag.
107
+ #
108
+ def javascript
109
+ end
110
+
111
+ # Specifies when should create a note for it.
112
+ # By default, it's valid.
113
+ #
114
+ def valid?
115
+ true
116
+ end
117
+
118
+ # Specifies when should create a fieldset for it, considering it's valid.
119
+ #
120
+ def has_fieldset?
121
+ self.respond_to?(:content)
122
+ end
123
+
124
+ # Some helpers to generate notes.
125
+ #
126
+ protected
127
+ # Return if Footnotes::Filter.prefix exists or not.
128
+ # Some notes only work with prefix set.
129
+ #
130
+ def prefix?
131
+ !Footnotes::Filter.prefix.blank?
132
+ end
133
+
134
+ # Escape HTML special characters.
135
+ #
136
+ def escape(text)
137
+ text.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
138
+ end
139
+
140
+ # Gets a bidimensional array and create a table.
141
+ # The first array is used as label.
142
+ #
143
+ def mount_table(array, options = {})
144
+ header = array.shift
145
+ return '' if array.empty?
146
+
147
+ header = header.collect{|i| escape(i.to_s.humanize) }
148
+ # array = array.collect { |a| a.collect { |b| c = b.to_s; escape(c) unless c == ""}}
149
+ rows = array.collect{|i| "<tr><td>#{i.join('</td><td>')}</td></tr>" }
150
+
151
+ <<-TABLE
152
+ <table #{hash_to_xml_attributes(options)}>
153
+ <thead><tr><th>#{header.join('</th><th>')}</th></tr></thead>
154
+ <tbody>#{rows.join}</tbody>
155
+ </table>
156
+ TABLE
157
+ end
158
+
159
+ # Mount table for hash, using name and value and adding a name_value class
160
+ # to the generated table.
161
+ #
162
+ def mount_table_for_hash(hash, options={})
163
+ rows = []
164
+ hash.each do |key, value|
165
+ rows << [ key.to_sym.inspect, escape(value.inspect) ]
166
+ end
167
+ mount_table(rows.unshift(['Name', 'Value']), {:class => 'name_value'}.merge(options))
168
+ end
169
+
170
+ def hash_to_xml_attributes(hash)
171
+ newstring = ""
172
+ hash.each do |key, value|
173
+ newstring << "#{key.to_s}=\"#{value.gsub('"','\"')}\" "
174
+ end
175
+ return newstring
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,60 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class AssignsNote < AbstractNote
6
+ @@ignored_assigns = [
7
+ :@real_format,
8
+ :@before_filter_chain_aborted,
9
+ :@performed_redirect,
10
+ :@performed_render,
11
+ :@_params,
12
+ :@_response,
13
+ :@url,
14
+ :@template,
15
+ :@_request,
16
+ :@db_rt_before_render,
17
+ :@db_rt_after_render,
18
+ :@view_runtime
19
+ ]
20
+ cattr_accessor :ignored_assigns, :instance_writter => false
21
+
22
+ def initialize(controller)
23
+ @controller = controller
24
+ end
25
+
26
+ def title
27
+ "Assigns (#{assigns.size})"
28
+ end
29
+
30
+ def valid?
31
+ assigns
32
+ end
33
+
34
+ def content
35
+ rows = []
36
+ assigns.each do |key|
37
+ rows << [ key, escape(assigned_value(key)) ]
38
+ end
39
+ mount_table(rows.unshift(['Name', 'Value']), :class => 'name_values', :summary => "Debug information for #{title}")
40
+ end
41
+
42
+ protected
43
+
44
+ def assigns
45
+ assign = []
46
+ ignored = @@ignored_assigns
47
+
48
+ @controller.instance_variables.each {|x| assign << x.intern }
49
+ @controller.protected_instance_variables.each {|x| ignored << x.intern } if @controller.respond_to? :protected_instance_variables
50
+
51
+ assign -= ignored
52
+ return assign
53
+ end
54
+
55
+ def assigned_value(key)
56
+ @controller.instance_variable_get(key).inspect
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,72 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class ControllerNote < AbstractNote
6
+ def initialize(controller)
7
+ @controller = controller
8
+ end
9
+
10
+ def row
11
+ :edit
12
+ end
13
+
14
+ def link
15
+ if controller_filename && controller_line_number
16
+ escape(Footnotes::Filter.prefix(controller_filename, controller_line_number + 1, 3))
17
+ end
18
+ end
19
+
20
+ def valid?
21
+ prefix?
22
+ end
23
+
24
+ protected
25
+ # Some controller classes come with the Controller:: module and some don't
26
+ # (anyone know why? -- Duane)
27
+ def controller_filename
28
+ return @controller_filename if defined?(@controller_filename)
29
+ controller_name=@controller.class.to_s.underscore
30
+ controller_name='application' if controller_name=='application_controller'
31
+ if ActionController::Routing.respond_to? :controller_paths
32
+ @controller_filename=nil
33
+ ActionController::Routing.controller_paths.each do |controller_path|
34
+ full_controller_path = File.join(File.expand_path(controller_path), "#{controller_name}.rb")
35
+ @controller_filename=full_controller_path if File.exists?(full_controller_path)
36
+ end
37
+ #raise "File not found"
38
+ else
39
+ @controller_filename=File.join(File.expand_path(Rails.root), 'app', 'controllers', "#{controller_name}.rb").sub('/controllers/controllers/', '/controllers/')
40
+ end
41
+ @controller_filename
42
+ end
43
+
44
+ def controller_text
45
+ if controller_filename
46
+ @controller_text ||= IO.read(controller_filename)
47
+ end
48
+ end
49
+
50
+ def action_index
51
+ (controller_text =~ /def\s+#{@controller.action_name}[\s\(]/) if controller_text
52
+ end
53
+
54
+ def controller_line_number
55
+ lines_from_index(controller_text, action_index) || 0 if controller_text
56
+ end
57
+
58
+ def lines_from_index(string, index)
59
+ return nil if string.blank? || index.blank?
60
+
61
+ lines = string.respond_to?(:to_a) ? string.to_a : string.lines.to_a
62
+ running_length = 0
63
+ lines.each_with_index do |line, i|
64
+ running_length += line.length
65
+ if running_length > index
66
+ return i
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class CookiesNote < AbstractNote
6
+ def initialize(controller)
7
+ @cookies = (controller.__send__(:cookies) || {}).symbolize_keys
8
+ end
9
+
10
+ def title
11
+ "Cookies (#{@cookies.length})"
12
+ end
13
+
14
+ def content
15
+ mount_table_for_hash(@cookies, :summary => "Debug information for #{title}")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class EnvNote < AbstractNote
6
+ def initialize(controller)
7
+ @env = controller.request.env.dup
8
+ end
9
+
10
+ def content
11
+ env_data = @env.to_a.sort.unshift([:key, :value]).map do |k,v|
12
+ case k
13
+ when 'HTTP_COOKIE'
14
+ # Replace HTTP_COOKIE for a link
15
+ [k, '<a href="#" style="color:#009" onclick="Footnotes.hideAllAndToggle(\'cookies_debug_info\');return false;">See cookies on its tab</a>']
16
+ else
17
+ [k, escape(v.to_s)]
18
+ end
19
+ end
20
+
21
+ # Create the env table
22
+ mount_table(env_data)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class FilesNote < AbstractNote
6
+ def initialize(controller)
7
+ @files = scan_text(controller.response.body)
8
+ parse_files!
9
+ end
10
+
11
+ def row
12
+ :edit
13
+ end
14
+
15
+ def content
16
+ if @files.empty?
17
+ ""
18
+ else
19
+ "<ul><li>#{@files.join("</li><li>")}</li></ul>"
20
+ end
21
+ end
22
+
23
+ def valid?
24
+ prefix?
25
+ end
26
+
27
+ protected
28
+ def scan_text(text)
29
+ []
30
+ end
31
+
32
+ def parse_files!
33
+ @files.collect! do |filename|
34
+ if filename =~ %r{^/}
35
+ full_filename = File.join(File.expand_path(Rails.root), 'public', filename)
36
+ %[<a href="#{Footnotes::Filter.prefix(full_filename, 1, 1)}">#{filename}</a>]
37
+ else
38
+ %[<a href="#{filename}">#{filename}</a>]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class FiltersNote < AbstractNote
6
+ def initialize(controller)
7
+ @controller = controller
8
+ @parsed_filters = parse_filters
9
+ end
10
+
11
+ def legend
12
+ "Filter chain for #{@controller.class.to_s}"
13
+ end
14
+
15
+ def content
16
+ mount_table(@parsed_filters.unshift([:name, :type, :actions]), :summary => "Debug information for #{title}")
17
+ end
18
+
19
+ protected
20
+ # Get controller filter chain
21
+ #
22
+ def parse_filters
23
+ return [] # TODO @controller.class.filter_chain.collect do |filter|
24
+ # [parse_method(filter.method), filter.type.inspect, controller_filtered_actions(filter).inspect]
25
+ # end
26
+ end
27
+
28
+ # This receives a filter, creates a mock controller and check in which
29
+ # actions the filter is performed
30
+ #
31
+ def controller_filtered_actions(filter)
32
+ mock_controller = Footnotes::Extensions::MockController.new
33
+
34
+ return @controller.class.action_methods.select { |action|
35
+ mock_controller.action_name = action
36
+
37
+ #remove conditions (this would call a Proc on the mock_controller)
38
+ filter.options.merge!(:if => nil, :unless => nil)
39
+
40
+ filter.__send__(:should_run_callback?, mock_controller)
41
+ }.map(&:to_sym)
42
+ end
43
+
44
+ def parse_method(method = '')
45
+ escape(method.inspect.gsub(RAILS_ROOT, ''))
46
+ end
47
+ end
48
+ end
49
+
50
+ module Extensions
51
+ class MockController < Struct.new(:action_name); end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class GeneralNote < AbstractNote
6
+ def title
7
+ 'General Debug'
8
+ end
9
+
10
+ def legend
11
+ 'General (id="general_debug_info")'
12
+ end
13
+
14
+ def content
15
+ 'You can use this tab to debug other parts of your application, for example Javascript.'
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 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? && nil#@controller.active_layout TODO doesn't work with Rails 3
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,47 @@
1
+ require "#{File.dirname(__FILE__)}/abstract_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class LogNote < AbstractNote
6
+ @@log = []
7
+
8
+ def self.log(message)
9
+ @@log << message
10
+ end
11
+
12
+ def initialize(controller)
13
+ @controller = controller
14
+ end
15
+
16
+ def title
17
+ "Log (#{log.count("\n")})"
18
+ end
19
+
20
+ def content
21
+ escape(log.gsub(/\e\[.+?m/, '')).gsub("\n", '<br />')
22
+ end
23
+
24
+ def log
25
+ unless @log
26
+ @log = @@log.join('')
27
+ @@log = []
28
+ if rindex = @log.rindex('Processing '+@controller.class.name+'#'+@controller.action_name)
29
+ @log = @log[rindex..-1]
30
+ end
31
+ end
32
+ @log
33
+ end
34
+
35
+ module LoggingExtensions
36
+ def add(*args, &block)
37
+ logged_message = super
38
+ Footnotes::Notes::LogNote.log(logged_message)
39
+ logged_message
40
+ end
41
+ end
42
+
43
+ Rails.logger.extend LoggingExtensions
44
+ end
45
+ end
46
+ end
47
+
@@ -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, :summary => "Debug information for #{title}")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,56 @@
1
+ require "#{File.dirname(__FILE__)}/log_note"
2
+
3
+ module Footnotes
4
+ module Notes
5
+ class PartialsNote < LogNote
6
+ def initialize(controller)
7
+ super
8
+ @controller = controller
9
+ end
10
+ def row
11
+ :edit
12
+ end
13
+ def title
14
+ "Partials (#{partials.size})"
15
+ end
16
+ def content
17
+ rows = partials.map do |filename|
18
+ href = Footnotes::Filter.prefix(filename,1,1)
19
+ shortened_name=filename.gsub(File.join(Rails.root,"app/views/"),"")
20
+ [%{<a href="#{href}">#{shortened_name}</a>},"#{@partial_times[filename].sum}ms",@partial_counts[filename]]
21
+ end
22
+ mount_table(rows.unshift(%w(Partial Time Count)), :summary => "Partials for #{title}")
23
+ end
24
+
25
+ protected
26
+ #Generate a list of partials that were rendered, also build up render times and counts.
27
+ #This is memoized so we can use its information in the title easily.
28
+ def partials
29
+ @partials ||= begin
30
+ partials = []
31
+ @partial_counts = {}
32
+ @partial_times = {}
33
+ log_lines = log
34
+ log_lines.split("\n").each do |line|
35
+ if line =~ /Rendered (\S*) \(([\d\.]+)\S*?\)/
36
+ partial = $1
37
+ @controller.view_paths.each do |view_path|
38
+ path = File.join(view_path, "#{partial}*")
39
+ files = Dir.glob(path)
40
+ for file in files
41
+ #TODO figure out what format got rendered if theres multiple
42
+ @partial_times[file] ||= []
43
+ @partial_times[file] << $2.to_f
44
+ @partial_counts[file] ||= 0
45
+ @partial_counts[file] += 1
46
+ partials << file unless partials.include?(file)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ partials.reverse
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end