scharfie-rails-footnotes 3.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +56 -0
- data/MIT-LICENSE +21 -0
- data/README +147 -0
- data/Rakefile +57 -0
- data/init.rb +2 -0
- data/lib/rails-footnotes.rb +20 -0
- data/lib/rails-footnotes/assets/footnotes.css +26 -0
- data/lib/rails-footnotes/assets/footnotes.js +38 -0
- data/lib/rails-footnotes/backtracer.rb +34 -0
- data/lib/rails-footnotes/footnotes.rb +297 -0
- data/lib/rails-footnotes/notes/abstract_note.rb +163 -0
- data/lib/rails-footnotes/notes/components_note.rb +79 -0
- data/lib/rails-footnotes/notes/controller_note.rb +55 -0
- data/lib/rails-footnotes/notes/cookies_note.rb +19 -0
- data/lib/rails-footnotes/notes/env_note.rb +19 -0
- data/lib/rails-footnotes/notes/files_note.rb +44 -0
- data/lib/rails-footnotes/notes/filters_note.rb +53 -0
- data/lib/rails-footnotes/notes/general_note.rb +19 -0
- data/lib/rails-footnotes/notes/javascripts_note.rb +16 -0
- data/lib/rails-footnotes/notes/layout_note.rb +28 -0
- data/lib/rails-footnotes/notes/log_note.rb +36 -0
- data/lib/rails-footnotes/notes/params_note.rb +19 -0
- data/lib/rails-footnotes/notes/queries_note.rb +146 -0
- data/lib/rails-footnotes/notes/routes_note.rb +59 -0
- data/lib/rails-footnotes/notes/session_note.rb +15 -0
- data/lib/rails-footnotes/notes/stylesheets_note.rb +16 -0
- data/lib/rails-footnotes/notes/view_note.rb +35 -0
- data/rails-footnotes.gemspec +46 -0
- data/test/footnotes_test.rb +199 -0
- data/test/notes/abstract_note_test.rb +107 -0
- data/test/test_helper.rb +9 -0
- metadata +107 -0
@@ -0,0 +1,163 @@
|
|
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('&', '&').gsub('<', '<').gsub('>', '>')
|
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
|
+
rows = array.collect{|i| "<tr><td>#{i.join('</td><td>')}</td></tr>" }
|
149
|
+
|
150
|
+
<<-TABLE
|
151
|
+
<table #{hash_to_xml_attributes(options)}>
|
152
|
+
<thead><tr><th>#{header.join('</th><th>')}</th></tr></thead>
|
153
|
+
<tbody>#{rows.join}</tbody>
|
154
|
+
</table>
|
155
|
+
TABLE
|
156
|
+
end
|
157
|
+
|
158
|
+
def hash_to_xml_attributes(hash)
|
159
|
+
return hash.collect{ |key, value| "#{key.to_s}=\"#{value.gsub('"','\"')}\"" }.join(' ')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/abstract_note"
|
2
|
+
require "#{File.dirname(__FILE__)}/controller_note"
|
3
|
+
require "#{File.dirname(__FILE__)}/view_note"
|
4
|
+
require "#{File.dirname(__FILE__)}/params_note"
|
5
|
+
|
6
|
+
module Footnotes
|
7
|
+
module Notes
|
8
|
+
module ComponentsNote
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def to_sym
|
15
|
+
@note_sym ||= "#{self.title.underscore}_component_#{(rand*1000).to_i}".to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def title
|
19
|
+
@note_title ||= self.name.match(/^Footnotes::Notes::(\w+)ComponentNote$/)[1]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(controller)
|
24
|
+
super
|
25
|
+
@controller = controller
|
26
|
+
end
|
27
|
+
|
28
|
+
def row
|
29
|
+
"#{@controller.controller_name.camelize}##{@controller.action_name.camelize} Component"
|
30
|
+
end
|
31
|
+
|
32
|
+
def legend
|
33
|
+
"#{super} for #{row}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ControllerComponentNote < ControllerNote; include ComponentsNote; end
|
38
|
+
class ViewComponentNote < ViewNote; include ComponentsNote; end
|
39
|
+
class ParamsComponentNote < ParamsNote; include ComponentsNote; end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Components
|
43
|
+
|
44
|
+
def self.included(base)
|
45
|
+
base.class_eval do
|
46
|
+
alias_method_chain :add_footnotes!, :component
|
47
|
+
Footnotes::Filter.notes.delete(:components)
|
48
|
+
@@component_notes = [ :controller, :view, :params ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_footnotes_with_component!
|
53
|
+
if component_request?
|
54
|
+
initialize_component_notes!
|
55
|
+
Footnotes::Filter.notes.unshift(*@notes)
|
56
|
+
else
|
57
|
+
add_footnotes_without_component!
|
58
|
+
Footnotes::Filter.notes.delete_if {|note| note.class.to_s =~ /(ComponentNote)$/}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
def initialize_component_notes!
|
64
|
+
@@component_notes.flatten.each do |note|
|
65
|
+
begin
|
66
|
+
note = eval("Footnotes::Notes::#{note.to_s.camelize}ComponentNote").new(@controller) if note.is_a?(Symbol) || note.is_a?(String)
|
67
|
+
@notes << note if note.respond_to?(:valid?) && note.valid?
|
68
|
+
rescue Exception => e
|
69
|
+
# Discard note if it has a problem
|
70
|
+
self.class.log_error("Footnotes #{note.to_s.camelize}ComponentNote Exception", e)
|
71
|
+
next
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Footnotes::Filter.__send__ :include, Footnotes::Components if Footnotes::Filter.notes.include?(:components)
|
@@ -0,0 +1,55 @@
|
|
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
|
+
escape(Footnotes::Filter.prefix(controller_filename, controller_line_number + 1, 3))
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
prefix?
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
# Some controller classes come with the Controller:: module and some don't
|
24
|
+
# (anyone know why? -- Duane)
|
25
|
+
def controller_filename
|
26
|
+
File.join(File.expand_path(RAILS_ROOT), 'app', 'controllers', "#{@controller.class.to_s.underscore}.rb").sub('/controllers/controllers/', '/controllers/')
|
27
|
+
end
|
28
|
+
|
29
|
+
def controller_text
|
30
|
+
@controller_text ||= IO.read(controller_filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
def action_index
|
34
|
+
(controller_text =~ /def\s+#{@controller.action_name}[\s\(]/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def controller_line_number
|
38
|
+
lines_from_index(controller_text, action_index) || 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def lines_from_index(string, index)
|
42
|
+
return nil if string.blank? || index.blank?
|
43
|
+
|
44
|
+
lines = string.to_a
|
45
|
+
running_length = 0
|
46
|
+
lines.each_with_index do |line, i|
|
47
|
+
running_length += line.length
|
48
|
+
if running_length > index
|
49
|
+
return i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
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
|
+
escape(@cookies.inspect)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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
|
+
# Replace HTTP_COOKIE for a link
|
12
|
+
@env['HTTP_COOKIE'] = '<a href="#" style="color:#009" onclick="Footnotes.hideAllAndToggle(\'cookies_debug_info\');return false;">See cookies on its tab</a>'
|
13
|
+
|
14
|
+
# Create the env table
|
15
|
+
mount_table(@env.to_a.sort.unshift([:key, :value]))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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]))
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
# Get controller filter chain
|
21
|
+
#
|
22
|
+
def parse_filters
|
23
|
+
return @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
|