rails6-footnotes 5.0.0

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.
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