module Footnotes class Filter @@no_style = false @@multiple_notes = false @@klasses = [] # Default link prefix is textmate @@prefix = 'txmt://open?url=file://%s&line=%d&column=%d' # Edit notes @@notes = [ :controller, :view, :layout, :stylesheets, :javascripts ] # Show notes @@notes += [ :assigns, :session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general ] # Change queries for rpm note when available if defined?(NewRelic) @@notes.delete(:queries) @@notes << :rpm end # :no_style => If you don't want the style to be appended to your pages # :notes => Class variable that holds the notes to be processed # :prefix => Prefix appended to FootnotesLinks # :multiple_notes => Set to true if you want to open several notes at the same time cattr_accessor :no_style, :notes, :prefix, :multiple_notes class << self # Method called to start the notes # It's a before filter prepend in the controller # def before(controller) Footnotes::Filter.start!(controller) end # Method that calls Footnotes to attach its contents # def after(controller) filter = Footnotes::Filter.new(controller) filter.add_footnotes! filter.close!(controller) end # Calls the class method start! in each note # Sometimes notes need to set variables or clean the environment to work properly # This method allows this kind of setup # def start!(controller) @@klasses = [] each_with_rescue(@@notes.flatten) do |note| klass = "Footnotes::Notes::#{note.to_s.camelize}Note".constantize klass.start!(controller) if klass.respond_to?(:start!) @@klasses << klass end end # Process notes, discarding only the note if any problem occurs # def each_with_rescue(notes) delete_me = [] notes.each do |note| begin yield note rescue Exception => e # Discard note if it has a problem log_error("Footnotes #{note.to_s.camelize}Note Exception", e) delete_me << note next end end delete_me.each{ |note| notes.delete(note) } return notes end # Logs the error using specified title and format # def log_error(title, exception) RAILS_DEFAULT_LOGGER.error "#{title}: #{exception}\n#{exception.backtrace.join("\n")}" end # If none argument is sent, simply return the prefix. # Otherwise, replace the args in the prefix. # def prefix(*args) if args.empty? @@prefix else format(@@prefix, *args) end end end def initialize(controller) @controller = controller @template = controller.instance_variable_get(:@template) @body = controller.response.body @notes = [] end def add_footnotes! add_footnotes_without_validation! if valid? rescue Exception => e # Discard footnotes if there are any problems self.class.log_error("Footnotes Exception", e) end # Calls the class method close! in each note # Sometimes notes need to finish their work even after being read # This method allows this kind of work # def close!(controller) each_with_rescue(@@klasses) do |klass| klass.close!(controller) end end protected def valid? performed_render? && valid_format? && valid_content_type? && @body.is_a?(String) && !component_request? && !xhr? end def add_footnotes_without_validation! initialize_notes! insert_styles unless @@no_style insert_footnotes end def initialize_notes! each_with_rescue(@@klasses) do |klass| note = klass.new(@controller) @notes << note if note.respond_to?(:valid?) && note.valid? end end def performed_render? @controller.instance_variable_get(:@performed_render) end def valid_format? [:html,:rhtml,:xhtml,:rxhtml].include?(@template.template_format.to_sym) end def valid_content_type? c = @controller.response.headers['Content-Type'].to_s (c.empty? || c =~ /html/) end def component_request? @controller.instance_variable_get(:@parent_controller) end def xhr? @controller.request.xhr? end # # Insertion methods # def insert_styles insert_text :before, /<\/head>/i, <<-HTML HTML end def insert_footnotes # Fieldsets method should be called first content = fieldsets footnotes_html = <<-HTML