merb_footnotes 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jacques Crocker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,14 @@
1
+ merb_footnotes
2
+ ==============
3
+
4
+
5
+
6
+
7
+
8
+ Problems:
9
+ --------
10
+ - Filters
11
+ - Components
12
+ - Controller
13
+ - Queries
14
+ - Layout
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ require 'merb-core'
5
+ require 'merb-core/tasks/merb'
6
+
7
+ GEM_NAME = "merb_footnotes"
8
+ GEM_VERSION = "0.1"
9
+ AUTHOR = "Jacques Crocker"
10
+ EMAIL = "merbjedi@gmail.com"
11
+ HOMEPAGE = "http://merbjedi.com/"
12
+ SUMMARY = "Provides an extensible footnotes debugging bar to Merb development mode"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.rubyforge_project = 'merb'
16
+ s.name = GEM_NAME
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.add_dependency('merb', '>= 1.0')
27
+ s.require_path = 'lib'
28
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{lib,spec}/**/*")
29
+
30
+ end
31
+
32
+ Rake::GemPackageTask.new(spec) do |pkg|
33
+ pkg.gem_spec = spec
34
+ end
35
+
36
+ desc "install the plugin as a gem"
37
+ task :install do
38
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
39
+ end
40
+
41
+ desc "Uninstall the gem"
42
+ task :uninstall do
43
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
44
+ end
45
+
46
+ desc "Create a gemspec file"
47
+ task :gemspec do
48
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
49
+ file.puts spec.to_ruby
50
+ end
51
+ end
@@ -0,0 +1,79 @@
1
+ # Copyright (c) 2008 FiveRuns Corporation
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ if defined?(Merb::Plugins)
23
+ # Merb gives you a Merb::Plugins.config hash...feel free to put your stuff in your piece of it
24
+ Merb::Plugins.config[:merb_footnotes] = {
25
+ :prefix => RUBY_PLATFORM.include?('darwin') ? 'txmt://open?url=file://' : "file://",
26
+ :multiple_notes => false,
27
+ :no_style => false,
28
+ :notes => [
29
+ # :components,
30
+ :controller,
31
+ :view,
32
+ :layout,
33
+ :partials,
34
+ :stylesheets,
35
+ :javascripts,
36
+ :session,
37
+ :cookies,
38
+ :params,
39
+ :filters,
40
+ :routes,
41
+ :env,
42
+ :queries,
43
+ :log
44
+ ]
45
+ }
46
+
47
+ Merb::BootLoader.before_app_loads do
48
+ if Merb.env?(:development) || Merb::Plugins.config[:merb_footnotes][:force]
49
+ Dir[File.join(File.dirname(__FILE__), 'merb_footnotes', 'notes','*.rb')].each do |note|
50
+ require note
51
+ end
52
+ require 'merb_footnotes/filter'
53
+ require 'merb_footnotes/formatter'
54
+ require 'merb_footnotes/instrumentation'
55
+
56
+ if Merb::Config[:adapter] != 'irb'
57
+ Merb.logger.info "Instrumenting with Merb Internals with Merb Footnotes"
58
+ ::Merb::Controller.extend(MerbFootnotes::Instrumentation::Merb::Controller)
59
+ if defined?(::DataMapper)
60
+ ::DataMapper::Repository.extend(MerbFootnotes::Instrumentation::DataMapper::Repository)
61
+ end
62
+ else
63
+ Merb.logger.info "Not instrumenting with Merb Footnotes (adapter is '#{Merb::Config[:adapter]}')"
64
+ end
65
+
66
+ Merb::Controller.before do
67
+ MerbFootnotes::Filter.start!(self) # self is the currently executing controller
68
+ end
69
+ Merb::Controller.after do
70
+ filter = MerbFootnotes::Filter.new(self) # self is the currently executing controller
71
+ filter.add_footnotes!
72
+ filter.close!(self)
73
+ end
74
+ end
75
+ end
76
+
77
+ Merb::BootLoader.after_app_loads do
78
+ end
79
+ end
@@ -0,0 +1,258 @@
1
+ module MerbFootnotes
2
+ class Filter
3
+
4
+ def self.notes
5
+ @@notes ||= begin
6
+ note_list = Merb::Plugins.config[:merb_footnotes][:notes].to_a
7
+ if Merb::Plugins.config[:merb_footnotes][:append_notes].is_a? Array
8
+ # Use custom list if set
9
+ note_list += Merb::Plugins.config[:merb_footnotes][:append_notes]
10
+ end
11
+ note_list.flatten.compact
12
+ end
13
+ end
14
+
15
+ # Calls the class method start! in each note
16
+ # Sometimes notes need to set variables or clean the environment to work properly
17
+ # This method allows this kind of setup
18
+ #
19
+ def self.start!(controller)
20
+ each_with_rescue(self.notes) do |note|
21
+ klass = eval("MerbFootnotes::Notes::#{note.to_s.camel_case}Note") if note.is_a?(Symbol) || note.is_a?(String)
22
+ if klass.respond_to?(:start!)
23
+ klass.start!(controller)
24
+ end
25
+ end
26
+ end
27
+
28
+ # Process notes, discarding only the note if any problem occurs
29
+ #
30
+ def self.each_with_rescue(notes)
31
+ delete_me = []
32
+
33
+ notes.each do |note|
34
+ begin
35
+ yield note
36
+ rescue Exception => e
37
+ # Discard note if it has a problem
38
+ log_error("Footnotes #{note.to_s.camel_case}Note Exception", e)
39
+ delete_me << note
40
+ next
41
+ end
42
+ end
43
+
44
+ delete_me.each{ |note| notes.delete(note) }
45
+ end
46
+
47
+ # Logs the error using specified title and format
48
+ #
49
+ def self.log_error(title, exception)
50
+ Merb.logger.error "#{title}: #{exception}\n#{exception.backtrace.join("\n")}"
51
+ end
52
+
53
+ def initialize(controller)
54
+ @controller = controller
55
+ @body = controller.body
56
+ @notes = []
57
+ end
58
+
59
+ def add_footnotes!
60
+ add_footnotes_without_validation! if valid?
61
+ rescue Exception => e
62
+ # Discard footnotes if there are any problems
63
+ self.class.log_error("Footnotes Exception", e)
64
+ end
65
+
66
+ # Calls the class method close! in each note
67
+ # Sometimes notes need to finish their work even after being read
68
+ # This method allows this kind of work
69
+ #
70
+ def close!(controller)
71
+ each_with_rescue(@notes) do |note|
72
+ note.class.close!(controller)
73
+ end
74
+ end
75
+
76
+ protected
77
+ def valid?
78
+ unless @footnotes_rendered
79
+ valid_content_type? && @body.is_a?(String) && !xhr?
80
+ @footnotes_rendered = true
81
+ end
82
+ end
83
+
84
+ def add_footnotes_without_validation!
85
+ initialize_notes!
86
+ insert_styles unless Merb::Plugins.config[:merb_footnotes][:no_style]
87
+ insert_footnotes
88
+ end
89
+
90
+ def initialize_notes!
91
+ each_with_rescue(self.class.notes) do |note|
92
+ note = eval("MerbFootnotes::Notes::#{note.to_s.camel_case}Note").new(@controller) if note.is_a?(Symbol) || note.is_a?(String)
93
+ @notes << note if note.respond_to?(:valid?) && note.valid?
94
+ end
95
+ end
96
+
97
+ def valid_content_type?
98
+ c = @controller.content_type.to_s
99
+ (c.nil? || c =~ /html/)
100
+ end
101
+
102
+ def xhr?
103
+ @controller.request.xhr?
104
+ end
105
+
106
+ #
107
+ # Insertion methods
108
+ #
109
+
110
+ def insert_styles
111
+ insert_text :before, /<\/head>/i, <<-HTML
112
+ <!-- Footnotes Style -->
113
+ <style type="text/css">
114
+ #footnotes_debug {margin: 2em 0 1em 0; text-align: center; color: #444; line-height: 16px;}
115
+ #footnotes_debug a {text-decoration: none; color: #444; line-height: 18px;}
116
+ #footnotes_debug table {text-align: center;}
117
+ #footnotes_debug table td {padding: 0 5px;}
118
+ #footnotes_debug tbody {text-align: left;}
119
+ #footnotes_debug legend {background-color: #FFF;}
120
+ #footnotes_debug fieldset {text-align: left; border: 1px dashed #aaa; padding: 0.5em 1em 1em 1em; margin: 1em 2em; color: #444; background-color: #FFF;}
121
+ /* Aditional Stylesheets */
122
+ #{@notes.map{|s| s.stylesheet}.compact.join("\n")}
123
+ </style>
124
+ <!-- End Footnotes Style -->
125
+ HTML
126
+ end
127
+
128
+ def insert_footnotes
129
+ # Fieldsets method should be called first
130
+ content = fieldsets
131
+
132
+ footnotes_html = <<-HTML
133
+ <!-- Footnotes -->
134
+ <div style="clear:both"></div>
135
+ <div id="footnotes_debug">
136
+ #{links}
137
+ #{content}
138
+ <script type="text/javascript">
139
+ function footnotes_close(){
140
+ #{close unless Merb::Plugins.config[:merb_footnotes][:multiple_notes]}
141
+ }
142
+ function footnotes_toogle(id){
143
+ s = document.getElementById(id).style;
144
+ before = s.display;
145
+ footnotes_close();
146
+ s.display = (before != 'block') ? 'block' : 'none'
147
+ location.href = '#footnotes_debug';
148
+ }
149
+ /* Additional Javascript */
150
+ #{@notes.map{|n| n.javascript}.compact.join("\n")}
151
+ </script>
152
+ </div>
153
+ <!-- End Footnotes -->
154
+ HTML
155
+ if @body =~ %r{<div[^>]+id=['"]footnotes_holder['"][^>]*>}
156
+ # Insert inside the "footnotes_holder" div if it exists
157
+ insert_text :after, %r{<div[^>]+id=['"]footnotes_holder['"][^>]*>}, footnotes_html
158
+ else
159
+ # Otherwise, try to insert as the last part of the html body
160
+ insert_text :before, /<\/body>/i, footnotes_html
161
+ end
162
+ end
163
+
164
+ # Process notes to gets their links
165
+ #
166
+ def links
167
+ links = Hash.new([])
168
+ order = []
169
+ each_with_rescue(@notes) do |note|
170
+ order << note.row
171
+ links[note.row] += [link_helper(note)]
172
+ end
173
+
174
+ html = ''
175
+ order.uniq!
176
+ order.each do |row|
177
+ html << "#{row.is_a?(String) ? row : row.to_s.camel_case}: #{links[row].join(" | \n")}<br />"
178
+ end
179
+ html
180
+ end
181
+
182
+ # Process notes to get their content
183
+ #
184
+ def fieldsets
185
+ content = ''
186
+ each_with_rescue(@notes) do |note|
187
+ next unless note.fieldset?
188
+ content << <<-HTML
189
+ <fieldset id="#{note.to_sym}_debug_info" style="display: none">
190
+ <legend>#{note.legend}</legend>
191
+ <div>#{note.content}</div>
192
+ </fieldset>
193
+ HTML
194
+ end
195
+ content
196
+ end
197
+
198
+ # Process notes to get javascript code to close them all
199
+ # This method is used with multiple_notes is false
200
+ #
201
+ def close
202
+ javascript = ''
203
+ each_with_rescue(@notes) do |note|
204
+ next unless note.fieldset?
205
+ javascript << close_helper(note)
206
+ end
207
+ javascript
208
+ end
209
+
210
+ #
211
+ # Helpers
212
+ #
213
+
214
+ # Helper that creates the javascript code to close the note
215
+ #
216
+ def close_helper(note)
217
+ "document.getElementById('#{note.to_sym}_debug_info').style.display = 'none'\n"
218
+ end
219
+
220
+ # Helper that creates the link and javascript code when note is clicked
221
+ #
222
+ def link_helper(note)
223
+ onclick = note.onclick
224
+ unless href = note.link
225
+ href = '#'
226
+ onclick ||= "footnotes_toogle('#{note.to_sym}_debug_info');return false;" if note.fieldset?
227
+ end
228
+
229
+ "<a href=\"#{href}\" onclick=\"#{onclick}\">#{note.title}</a>"
230
+ end
231
+
232
+ # Inserts text in to the body of the document
233
+ # +pattern+ is a Regular expression which, when matched, will cause +new_text+
234
+ # to be inserted before or after the match. If no match is found, +new_text+ is appended
235
+ # to the body instead. +position+ may be either :before or :after
236
+ #
237
+ def insert_text(position, pattern, new_text)
238
+ index = case pattern
239
+ when Regexp
240
+ if match = @body.match(pattern)
241
+ match.offset(0)[position == :before ? 0 : 1]
242
+ else
243
+ @body.size
244
+ end
245
+ else
246
+ pattern
247
+ end
248
+ @body.insert index, new_text
249
+ end
250
+
251
+ # Instance each_with_rescue method
252
+ #
253
+ def each_with_rescue(*args, &block)
254
+ self.class.each_with_rescue(*args, &block)
255
+ end
256
+
257
+ end
258
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright (c) 2008 FiveRuns Corporation
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ module MerbFootnotes::Formatter
23
+ # def self.pretty(value)
24
+ # CGI.escapeHTML(PP.pp(value, ''))
25
+ # end
26
+
27
+ def self.strip_root(text)
28
+ pattern = /^#{Regexp.quote Merb.root}\/?/o
29
+ if text =~ pattern
30
+ result = text.sub(pattern, '')
31
+ in_app = result !~ /^gems\//
32
+ return [in_app, result]
33
+ end
34
+
35
+ [false, text]
36
+ end
37
+
38
+ # TODO: Refactor
39
+ def self.editor_link_line(line, show_filename_only = false)
40
+ filename, number, extra = line.match(/^(.+?):(\d+)(?::in\b(.*?))?/)[1, 2]
41
+ in_app, line = strip_root(line)
42
+ name = if line.size > 87
43
+ "&hellip;#{CGI.escapeHTML line.sub(/^.*?(.{84})$/, '\1')}"
44
+ else
45
+ line
46
+ end
47
+ name = if in_app
48
+ if name =~ /`/
49
+ name.sub(/^(.*?)\s+`(.*?)'$/, %q(<span class='tuneup-app-line'>\1</span> `<b class='tuneup-app-line'>\2</b>'))
50
+ else
51
+ %(<span class='tuneup-app-line'>#{name}</span>)
52
+ end
53
+ else
54
+ name.sub(/([^\/\\]+\.\S+:\d+:in)\s+`(.*?)'$/, %q(\1 `<b>\2</b>'))
55
+ end
56
+
57
+ if show_filename_only
58
+ name = name.to_s.split("/").last
59
+ end
60
+
61
+ %(<a title='%s' href='txmt://open/?url=file://%s&line=%d'>%s</a>%s) % [CGI.escapeHTML(line), filename, number, name, extra]
62
+ end
63
+ end