rails6-footnotes 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +25 -0
  3. data/.gitignore +15 -0
  4. data/.rspec.example +1 -0
  5. data/CHANGELOG +129 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.lock +187 -0
  8. data/MIT-LICENSE +21 -0
  9. data/README.rdoc +188 -0
  10. data/Rakefile +18 -0
  11. data/bin/rake +29 -0
  12. data/bin/rspec +29 -0
  13. data/gemfiles/Gemfile.rails-3.2.22 +5 -0
  14. data/gemfiles/Gemfile.rails-4.0.x +6 -0
  15. data/gemfiles/Gemfile.rails-4.1.x +5 -0
  16. data/gemfiles/Gemfile.rails-4.2.x +5 -0
  17. data/gemfiles/Gemfile.rails-edge +5 -0
  18. data/lib/generators/rails_footnotes/install_generator.rb +14 -0
  19. data/lib/generators/templates/rails_footnotes.rb +26 -0
  20. data/lib/rails-footnotes.rb +68 -0
  21. data/lib/rails-footnotes/abstract_note.rb +178 -0
  22. data/lib/rails-footnotes/each_with_rescue.rb +36 -0
  23. data/lib/rails-footnotes/extension.rb +24 -0
  24. data/lib/rails-footnotes/filter.rb +359 -0
  25. data/lib/rails-footnotes/notes/all.rb +1 -0
  26. data/lib/rails-footnotes/notes/assigns_note.rb +60 -0
  27. data/lib/rails-footnotes/notes/controller_note.rb +55 -0
  28. data/lib/rails-footnotes/notes/cookies_note.rb +17 -0
  29. data/lib/rails-footnotes/notes/env_note.rb +24 -0
  30. data/lib/rails-footnotes/notes/files_note.rb +49 -0
  31. data/lib/rails-footnotes/notes/filters_note.rb +51 -0
  32. data/lib/rails-footnotes/notes/javascripts_note.rb +16 -0
  33. data/lib/rails-footnotes/notes/layout_note.rb +26 -0
  34. data/lib/rails-footnotes/notes/log_note.rb +47 -0
  35. data/lib/rails-footnotes/notes/log_note/note_logger.rb +59 -0
  36. data/lib/rails-footnotes/notes/params_note.rb +21 -0
  37. data/lib/rails-footnotes/notes/partials_note.rb +38 -0
  38. data/lib/rails-footnotes/notes/queries_note.rb +121 -0
  39. data/lib/rails-footnotes/notes/routes_note.rb +60 -0
  40. data/lib/rails-footnotes/notes/session_note.rb +27 -0
  41. data/lib/rails-footnotes/notes/stylesheets_note.rb +16 -0
  42. data/lib/rails-footnotes/notes/view_note.rb +41 -0
  43. data/lib/rails-footnotes/version.rb +3 -0
  44. data/lib/rails6-footnotes.rb +1 -0
  45. data/rails-footnotes.gemspec +22 -0
  46. data/spec/abstract_note_spec.rb +89 -0
  47. data/spec/app/assets/config/manifest.js +2 -0
  48. data/spec/app/assets/javascripts/foobar.js +1 -0
  49. data/spec/app/assets/stylesheets/foobar.css +0 -0
  50. data/spec/app/views/files/index.html.erb +1 -0
  51. data/spec/app/views/layouts/application.html.erb +12 -0
  52. data/spec/app/views/partials/_foo.html.erb +1 -0
  53. data/spec/app/views/partials/index.html.erb +1 -0
  54. data/spec/controllers/files_note_controller_spec.rb +38 -0
  55. data/spec/controllers/footnotes_controller_spec.rb +128 -0
  56. data/spec/controllers/log_note_controller_spec.rb +32 -0
  57. data/spec/controllers/partials_note_controller_spec.rb +28 -0
  58. data/spec/env_note_spec.rb +73 -0
  59. data/spec/fixtures/html_download.html +5 -0
  60. data/spec/footnotes_spec.rb +234 -0
  61. data/spec/notes/assigns_note_spec.rb +50 -0
  62. data/spec/notes/controller_note_spec.rb +12 -0
  63. data/spec/notes/files_note_spec.rb +26 -0
  64. data/spec/notes/javascripts_note_spec.rb +18 -0
  65. data/spec/notes/stylesheets_note_spec.rb +19 -0
  66. data/spec/notes/view_note_spec.rb +12 -0
  67. data/spec/spec_helper.rb +68 -0
  68. metadata +153 -0
@@ -0,0 +1,24 @@
1
+ require 'active_support/concern'
2
+
3
+ module Footnotes
4
+ module RailsFootnotesExtension
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ prepend_before_action :rails_footnotes_before_filter
9
+ after_action :rails_footnotes_after_filter
10
+ end
11
+
12
+ def rails_footnotes_before_filter
13
+ Footnotes::Filter.start!(self) if Footnotes.enabled?(self)
14
+ end
15
+
16
+ def rails_footnotes_after_filter
17
+ return unless Footnotes.enabled?(self)
18
+
19
+ filter = Footnotes::Filter.new(self)
20
+ filter.add_footnotes!
21
+ filter.close!(self)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,359 @@
1
+ module Footnotes
2
+ class Filter
3
+ @@no_style = false
4
+ @@multiple_notes = false
5
+ @@klasses = []
6
+ @@lock_top_right = false
7
+ @@font_size = '11px'
8
+
9
+ # Default link prefix is textmate
10
+ @@prefix = 'txmt://open?url=file://%s&line=%d&column=%d'
11
+
12
+ # Edit notes
13
+ @@notes = [ :controller, :view, :layout, :partials, :stylesheets, :javascripts ]
14
+ # Show notes
15
+ @@notes += [ :assigns, :session, :cookies, :params, :filters, :routes, :env, :queries, :log]
16
+
17
+ # :no_style => If you don't want the style to be appended to your pages
18
+ # :notes => Class variable that holds the notes to be processed
19
+ # :prefix => Prefix appended to FootnotesLinks
20
+ # :multiple_notes => Set to true if you want to open several notes at the same time
21
+ # :lock_top_right => Lock a btn to toggle notes to the top right of the browser
22
+ # :font_size => CSS font-size property
23
+ cattr_accessor :no_style, :notes, :prefix, :multiple_notes, :lock_top_right, :font_size
24
+
25
+ class << self
26
+ include Footnotes::EachWithRescue
27
+
28
+ # Calls the class method start! in each note
29
+ # Sometimes notes need to set variables or clean the environment to work properly
30
+ # This method allows this kind of setup
31
+ #
32
+ def start!(controller)
33
+ self.each_with_rescue(Footnotes.before_hooks) {|hook| hook.call(controller, self)}
34
+
35
+ @@klasses = []
36
+ self.each_with_rescue(@@notes.flatten) do |note|
37
+ klass = "Footnotes::Notes::#{note.to_s.camelize}Note".constantize
38
+ klass.start!(controller) if klass.respond_to?(:start!)
39
+ @@klasses << klass
40
+ end
41
+ end
42
+
43
+ # If none argument is sent, simply return the prefix.
44
+ # Otherwise, replace the args in the prefix.
45
+ #
46
+ def prefix(*args)
47
+ if args.empty?
48
+ @@prefix
49
+ else
50
+ args.map! { |arg| arg.to_s.split("/").map{|s| ERB::Util.url_encode(s) }.join("/") }
51
+
52
+ if @@prefix.respond_to? :call
53
+ @@prefix.call *args
54
+ else
55
+ format(@@prefix, *args)
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ def initialize(controller)
63
+ @controller = controller
64
+ @template = controller.instance_variable_get(:@template)
65
+ @notes = []
66
+
67
+ revert_pos(controller.response_body) do
68
+ @body = controller.response.body
69
+ end
70
+ end
71
+
72
+ def add_footnotes!
73
+ add_footnotes_without_validation! if valid?
74
+ rescue Exception => e
75
+ # Discard footnotes if there are any problems
76
+ self.class.log_error("Footnotes Exception", e)
77
+ end
78
+
79
+ # Calls the class method close! in each note
80
+ # Sometimes notes need to finish their work even after being read
81
+ # This method allows this kind of work
82
+ #
83
+ def close!(controller)
84
+ self.each_with_rescue(@@klasses) {|klass| klass.close!(controller)}
85
+ self.each_with_rescue(Footnotes.after_hooks) {|hook| hook.call(controller, self)}
86
+ end
87
+
88
+ protected
89
+ def valid?
90
+ @body.is_a?(String) && performed_render? && valid_format? && valid_content_type? &&
91
+ !component_request? && !xhr? && !footnotes_disabled? && !attached_file?
92
+ end
93
+
94
+ def add_footnotes_without_validation!
95
+ initialize_notes!
96
+ insert_styles unless @@no_style
97
+ insert_footnotes
98
+ end
99
+
100
+ def initialize_notes!
101
+ each_with_rescue(@@klasses) do |klass|
102
+ note = klass.new(@controller)
103
+ @notes << note if note.valid?
104
+ end
105
+ end
106
+
107
+ def revert_pos(file)
108
+ return yield unless file.respond_to?(:pos) && file.respond_to?(:pos=)
109
+ original = file.pos
110
+ yield
111
+ file.pos = original
112
+ end
113
+
114
+ def performed_render?
115
+ @controller.respond_to?(:performed?) && @controller.performed?
116
+ end
117
+
118
+ def valid_format?
119
+ format = @controller.response.content_type
120
+ format.nil? || format.include?("text/html")
121
+ end
122
+
123
+ def valid_content_type?
124
+ c = @controller.response.headers['Content-Type'].to_s
125
+ (c.empty? || c =~ /html/)
126
+ end
127
+
128
+ def component_request?
129
+ @controller.instance_variable_get(:@parent_controller)
130
+ end
131
+
132
+ def xhr?
133
+ @controller.request.xhr?
134
+ end
135
+
136
+ def footnotes_disabled?
137
+ @controller.params[:footnotes] == "false"
138
+ end
139
+
140
+ def attached_file?
141
+ !!(@controller.headers['Content-Disposition'] =~ /attachment/)
142
+ end
143
+
144
+ #
145
+ # Insertion methods
146
+ #
147
+
148
+ def insert_styles
149
+ #TODO More customizable(reset.css, from file etc.)
150
+ if @@lock_top_right
151
+ extra_styles = <<-STYLES
152
+ #footnotes_debug {position: fixed; top: 0px; right: 0px; width: 100%; z-index: 10000; margin-top: 0;}
153
+ #footnotes_debug #toggle_footnotes {position: absolute; right: 0; top: 0; background: #fff; border: 1px solid #ccc; color: #9b1b1b; font-size: 20px; text-align: center; padding: 8px; opacity: 0.9;}
154
+ #footnotes_debug #toggle_footnotes:hover {opacity: 1;}
155
+ #footnotes_debug #all_footnotes {display: none; padding: 15px; background: #fff; box-shadow: 0 0 5px rgba(0,0,0,0.4);}
156
+ #footnotes_debug fieldset > div {max-height: 500px; overflow: scroll;}
157
+ STYLES
158
+ else
159
+ extra_styles = <<-STYLES
160
+ #footnotes_debug #toggle_footnotes {display: none;}
161
+ STYLES
162
+ end
163
+ insert_text :before, /<\/head>/i, <<-HTML
164
+ <!-- Footnotes Style -->
165
+ <style type="text/css">
166
+ #footnotes_debug {font-size: #{@@font_size}; font-family: Consolas, monaco, monospace; font-weight: normal; margin: 2em 0 1em 0; text-align: center; color: #444; line-height: 16px; background: #fff;}
167
+ #footnotes_debug th, #footnotes_debug td {color: #444; line-height: 18px;}
168
+ #footnotes_debug a {color: #9b1b1b; font-weight: inherit; text-decoration: none; line-height: 18px;}
169
+ #footnotes_debug table {text-align: left; width: 100%;}
170
+ #footnotes_debug table td {padding: 5px; border-bottom: 1px solid #ccc;}
171
+ #footnotes_debug table td strong {color: #9b1b1b;}
172
+ #footnotes_debug table th {padding: 5px; border-bottom: 1px solid #ccc;}
173
+ #footnotes_debug table tr:nth-child(2n) td {background: #f5f5f5;}
174
+ #footnotes_debug table tr:nth-child(2n + 1) td {background: #fff;}
175
+ #footnotes_debug tbody {text-align: left;}
176
+ #footnotes_debug .name_values td {vertical-align: top;}
177
+ #footnotes_debug legend {background-color: #fff;}
178
+ #footnotes_debug fieldset {text-align: left; border: 1px dashed #aaa; padding: 0.5em 1em 1em 1em; margin: 1em 2em; color: #444; background-color: #FFF;}
179
+ #{extra_styles}
180
+ /* Aditional Stylesheets */
181
+ #{@notes.map(&:stylesheet).compact.join("\n")}
182
+ </style>
183
+ <!-- End Footnotes Style -->
184
+ HTML
185
+ end
186
+
187
+ def insert_footnotes
188
+ # Fieldsets method should be called first
189
+ content = fieldsets
190
+ element_style = ''
191
+ if @@lock_top_right
192
+ element_style = 'style="display: none;"'
193
+ end
194
+ footnotes_html = <<-HTML
195
+ <!-- Footnotes -->
196
+ <div style="clear:both"></div>
197
+ <div id="footnotes_debug">
198
+ <a id="toggle_footnotes" href="#" onclick="Footnotes.toggle('all_footnotes'); return false;">fn</a>
199
+ <div id="all_footnotes" #{element_style}>
200
+ #{links}
201
+ #{content}
202
+ </div>
203
+ <script type="text/javascript">
204
+ var Footnotes = function() {
205
+
206
+ function hideAll(){
207
+ #{close unless @@multiple_notes}
208
+ }
209
+
210
+ function hideAllAndToggle(id) {
211
+ var n = note(id);
212
+ var display = n.style.display;
213
+ hideAll();
214
+ // Restore original display to allow toggling
215
+ n.style.display = display;
216
+ toggle(id)
217
+
218
+ location.href = '#footnotes_debug';
219
+ }
220
+
221
+ function note(id) {
222
+ return (document.getElementById(id));
223
+ }
224
+
225
+ function toggle(id){
226
+ var el = note(id);
227
+ if (el.style.display == 'none') {
228
+ Footnotes.show(el);
229
+ } else {
230
+ Footnotes.hide(el);
231
+ }
232
+ }
233
+
234
+ function show(element) {
235
+ element.style.display = 'block'
236
+ }
237
+
238
+ function hide(element) {
239
+ element.style.display = 'none'
240
+ }
241
+
242
+ return {
243
+ show: show,
244
+ hide: hide,
245
+ toggle: toggle,
246
+ hideAllAndToggle: hideAllAndToggle
247
+ }
248
+ }();
249
+ /* Additional Javascript */
250
+ #{@notes.map(&:javascript).compact.join("\n")}
251
+ </script>
252
+ </div>
253
+ <!-- End Footnotes -->
254
+ HTML
255
+
256
+ placeholder = /<div[^>]+id=['"]footnotes_holder['"][^>]*>/i
257
+ if @controller.response.body =~ placeholder
258
+ insert_text :after, placeholder, footnotes_html
259
+ else
260
+ insert_text :before, /<\/body>/i, footnotes_html
261
+ end
262
+ end
263
+
264
+ # Process notes to gets their links in their equivalent row
265
+ #
266
+ def links
267
+ links = Hash.new([])
268
+ order = []
269
+ each_with_rescue(@notes) do |note|
270
+ order << note.row
271
+ links[note.row] += [link_helper(note)]
272
+ end
273
+
274
+ html = ''
275
+ order.uniq!
276
+ order.each do |row|
277
+ html << "#{row.is_a?(String) ? row : row.to_s.camelize}: #{links[row].join(" | \n")}<br />"
278
+ end
279
+ html
280
+ end
281
+
282
+ # Process notes to get their content
283
+ #
284
+ def fieldsets
285
+ content = ''
286
+ each_with_rescue(@notes) do |note|
287
+ next unless note.has_fieldset?
288
+ content << <<-HTML
289
+ <fieldset id="#{note.to_sym}_debug_info" style="display: none">
290
+ <legend>#{note.legend}</legend>
291
+ <div>#{note.content}</div>
292
+ </fieldset>
293
+ HTML
294
+ end
295
+ content
296
+ end
297
+
298
+ # Process notes to get javascript code to close them.
299
+ # This method is only used when multiple_notes is false.
300
+ #
301
+ def close
302
+ javascript = ''
303
+ each_with_rescue(@notes) do |note|
304
+ next unless note.has_fieldset?
305
+ javascript << close_helper(note)
306
+ end
307
+ javascript
308
+ end
309
+
310
+ #
311
+ # Helpers
312
+ #
313
+
314
+ # Helper that creates the javascript code to close the note
315
+ #
316
+ def close_helper(note)
317
+ "Footnotes.hide(document.getElementById('#{note.to_sym}_debug_info'));\n"
318
+ end
319
+
320
+ # Helper that creates the link and javascript code when note is clicked
321
+ #
322
+ def link_helper(note)
323
+ onclick = note.onclick
324
+ unless href = note.link
325
+ href = '#'
326
+ onclick ||= "Footnotes.hideAllAndToggle('#{note.to_sym}_debug_info');return false;" if note.has_fieldset?
327
+ end
328
+
329
+ "<a href=\"#{href}\" onclick=\"#{onclick}\">#{note.title}</a>"
330
+ end
331
+
332
+ # Inserts text in to the body of the document
333
+ # +pattern+ is a Regular expression which, when matched, will cause +new_text+
334
+ # to be inserted before or after the match. If no match is found, +new_text+ is appended
335
+ # to the body instead. +position+ may be either :before or :after
336
+ #
337
+ def insert_text(position, pattern, new_text)
338
+ index = case pattern
339
+ when Regexp
340
+ if match = @controller.response.body.match(pattern)
341
+ match.offset(0)[position == :before ? 0 : 1]
342
+ else
343
+ @controller.response.body.size
344
+ end
345
+ else
346
+ pattern
347
+ end
348
+ newbody = @controller.response.body
349
+ newbody.insert index, new_text
350
+ @controller.response.body = newbody
351
+ end
352
+
353
+ # Instance each_with_rescue method
354
+ #
355
+ def each_with_rescue(*args, &block)
356
+ self.class.each_with_rescue(*args, &block)
357
+ end
358
+ end
359
+ end
@@ -0,0 +1 @@
1
+ Dir[File.join(File.dirname(__FILE__), '*_note.rb')].each {|note| require note}
@@ -0,0 +1,60 @@
1
+ module Footnotes
2
+ module Notes
3
+ class AssignsNote < AbstractNote
4
+ @@ignored_assigns = [
5
+ :@real_format,
6
+ :@before_filter_chain_aborted,
7
+ :@performed_redirect,
8
+ :@performed_render,
9
+ :@_params,
10
+ :@_response,
11
+ :@url,
12
+ :@template,
13
+ :@_request,
14
+ :@db_rt_before_render,
15
+ :@db_rt_after_render,
16
+ :@view_runtime,
17
+ :@marked_for_same_origin_verification
18
+ ]
19
+ cattr_accessor :ignored_assigns, :instance_writer => false
20
+ @@ignored_assigns_pattern = /^@_/
21
+ cattr_accessor :ignored_assigns_pattern, :instance_writer => false
22
+
23
+ def initialize(controller)
24
+ @controller = controller
25
+ end
26
+
27
+ def title
28
+ "Assigns (#{assigns.size})"
29
+ end
30
+
31
+ def valid?
32
+ assigns.present?
33
+ end
34
+
35
+ def content
36
+ mount_table(to_table, :summary => "Debug information for #{title}")
37
+ end
38
+
39
+ protected
40
+ def to_table
41
+ table = assigns.inject([]) do |rr, var|
42
+ class_name = assigned_value(var).class.name
43
+ var_name = var.to_s
44
+ rr << ["<strong>#{var.to_s}</strong>" + "<br /><em>#{class_name}</em>", escape(assigned_value(var).inspect)]
45
+ end
46
+
47
+ table.unshift(['Name', 'Value'])
48
+ end
49
+
50
+ def assigns
51
+ @assigns ||= @controller.instance_variables.map {|v| v.to_sym}.select {|v| v.to_s !~ ignored_assigns_pattern } - ignored_assigns
52
+ end
53
+
54
+ def assigned_value(key)
55
+ @controller.instance_variable_get(key)
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ module Footnotes
2
+ module Notes
3
+ class ControllerNote < AbstractNote
4
+ def initialize(controller)
5
+ @controller = controller
6
+ end
7
+
8
+ def row
9
+ :edit
10
+ end
11
+
12
+ def link
13
+ Footnotes::Filter.prefix(controller_filename, controller_line_number + 1, 3)
14
+ end
15
+
16
+ def valid?
17
+ prefix? && controller_filename && File.exists?(controller_filename)
18
+ end
19
+
20
+ protected
21
+ def controller_path
22
+ @controller_path = @controller.class.name.underscore
23
+ end
24
+
25
+ def controller_filename
26
+ @controller_filename ||= Gem.find_files(controller_path).first # tnx https://github.com/MasterLambaster
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.respond_to?(:to_a) ? string.to_a : string.lines.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