Class: Slimmer::Skin

Inherits:
Object
  • Object
show all
Defined in:
lib/slimmer/skin.rb,
lib/slimmer/test.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Skin

TODO: Extract the cache to something we can pass in instead of using true/false and an in-memory cache.



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/slimmer/skin.rb', line 7

def initialize options = {}
  @options = options
  @asset_host = options[:asset_host]

  @use_cache = options[:use_cache] || false
  @cache_ttl = options[:cache_ttl] || (15 * 60) # 15 mins
  @template_cache = LRUCache.new(:ttl => @cache_ttl) if @use_cache

  @logger = options[:logger] || NullLogger.instance
  @strict = options[:strict] || (%w{development test}.include?(ENV['RACK_ENV']))
end

Instance Attribute Details

#asset_hostObject

Returns the value of attribute asset_host.



3
4
5
# File 'lib/slimmer/skin.rb', line 3

def asset_host
  @asset_host
end

#loggerObject

Returns the value of attribute logger.



3
4
5
# File 'lib/slimmer/skin.rb', line 3

def logger
  @logger
end

#optionsObject

Returns the value of attribute options.



3
4
5
# File 'lib/slimmer/skin.rb', line 3

def options
  @options
end

#strictObject

Returns the value of attribute strict.



3
4
5
# File 'lib/slimmer/skin.rb', line 3

def strict
  @strict
end

#template_cacheObject

Returns the value of attribute template_cache.



3
4
5
# File 'lib/slimmer/skin.rb', line 3

def template_cache
  @template_cache
end

#use_cacheObject

Returns the value of attribute use_cache.



3
4
5
# File 'lib/slimmer/skin.rb', line 3

def use_cache
  @use_cache
end

Instance Method Details

#artefact_from_header(response) ⇒ Object



146
147
148
149
150
151
152
153
154
155
# File 'lib/slimmer/skin.rb', line 146

def artefact_from_header(response)
  if response.headers.include?(Headers::ARTEFACT_HEADER)
    Artefact.new JSON.parse(response.headers[Headers::ARTEFACT_HEADER])
  else
    nil
  end
rescue JSON::ParserError => e
  logger.error "Slimmer: Failed while parsing artefact header: #{[ e.message, e.backtrace ].flatten.join("\n")}"
  nil
end

#context(html, error) ⇒ Object



72
73
74
75
76
77
78
79
80
81
# File 'lib/slimmer/skin.rb', line 72

def context(html, error)
  context_size = 5
  lines = [""] + html.split("\n")
  from = [1, error.line - context_size].max
  to = [lines.size - 1, error.line + context_size].min
  context = (from..to).zip(lines[from..to]).map {|lineno, line| "%4d: %s" % [lineno, line] }
  marker = " " * (error.column - 1) + "-----v"
  context.insert(context_size, marker)
  context.join("\n")
end

#error(template_name, body) ⇒ Object



139
140
141
142
143
144
# File 'lib/slimmer/skin.rb', line 139

def error(template_name, body)
  processors = [
    Processors::TitleInserter.new()
  ]
  process(processors, body, template(template_name))
end

#ignorable?(error) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
86
# File 'lib/slimmer/skin.rb', line 83

def ignorable?(error)
  ignorable_codes = [801]
  ignorable_codes.include?(error.code) || error.message.match(/Element script embeds close tag/) || error.message.match(/Unexpected end tag : noscript/)
end

#load_template(name) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/slimmer/skin.rb', line 29

def load_template(template_name)
  url = template_url(template_name)
  source = open(url, "r:UTF-8", :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE).read
  if template_name =~ /\.raw/
    template = source
  else
    template = ERB.new(source).result binding
  end
  template
rescue OpenURI::HTTPError => e
  raise TemplateNotFoundException, "Unable to fetch: '#{template_name}' from '#{url}' because #{e}", caller
rescue Errno::ECONNREFUSED => e
  raise CouldNotRetrieveTemplate, "Unable to fetch: '#{template_name}' from '#{url}' because #{e}", caller
rescue SocketError => e
  raise CouldNotRetrieveTemplate, "Unable to fetch: '#{template_name}' from '#{url}' because #{e}", caller
end

#parse_html(html, description_for_error_message) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/slimmer/skin.rb', line 56

def parse_html(html, description_for_error_message)
  doc = Nokogiri::HTML.parse(html)
  if strict
    errors = doc.errors.select {|e| e.error?}.reject {|e| ignorable?(e)}
    if errors.size > 0
      error = errors.first
      message = "In #{description_for_error_message}: '#{error.message}' at line #{error.line} col #{error.column} (code #{error.code}).\n"
      message << "Add ?skip_slimmer=1 to the url to show the raw backend request.\n\n"
      message << context(html, error)
      raise message
    end
  end

  doc
end

#process(processors, body, template) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/slimmer/skin.rb', line 88

def process(processors,body,template)
  logger.debug "Slimmer: starting skinning process"
  src = parse_html(body.to_s, "backend response")
  dest = parse_html(template, "template")

  start_time = Time.now
  logger.debug "Slimmer: Start time = #{start_time}"
  processors.each do |p|
    processor_start_time = Time.now
    logger.debug "Slimmer: Processor #{p} started at #{processor_start_time}"
    begin
      p.filter(src,dest)
    rescue => e
      logger.error "Slimmer: Failed while processing #{p}: #{[ e.message, e.backtrace ].flatten.join("\n")}"
    end
    processor_end_time = Time.now
    process_time = processor_end_time - processor_start_time
    logger.debug "Slimmer: Processor #{p} ended at #{processor_end_time} (#{process_time}s)"
  end
  end_time = Time.now
  logger.debug "Slimmer: Skinning process completed at #{end_time} (#{end_time - start_time}s)"

  # this is a horrible fix to Nokogiri removing the closing </noscript> tag required by Google Website Optimizer.
  # http://www.google.com/support/websiteoptimizer/bin/answer.py?hl=en_us&answer=64418
  dest.to_html.sub(/<noscript rel=("|')placeholder("|')>/, "")
end

#report_parse_errors_if_strict!(nokogiri_doc, description_for_error_message) ⇒ Object



52
53
54
# File 'lib/slimmer/skin.rb', line 52

def report_parse_errors_if_strict!(nokogiri_doc, description_for_error_message)
  nokogiri_doc
end

#success(source_request, response, body) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/slimmer/skin.rb', line 115

def success(source_request, response, body)
  artefact = artefact_from_header(response)
  processors = [
    Processors::TitleInserter.new(),
    Processors::TagMover.new(),
    Processors::ConditionalCommentMover.new(),
    Processors::BodyInserter.new(options[:wrapper_id] || 'wrapper'),
    Processors::BodyClassCopier.new,
    Processors::HeaderContextInserter.new(),
    Processors::SectionInserter.new(artefact),
    Processors::GoogleAnalyticsConfigurator.new(response, artefact),
    Processors::RelatedItemsInserter.new(self, artefact),
    Processors::LogoClassInserter.new(artefact),
    Processors::ReportAProblemInserter.new(self, source_request.url),
    Processors::SearchIndexSetter.new(response),
    Processors::MetaViewportRemover.new(response),
    Processors::CampaignNotificationInserter.new(self, response.headers),
    Processors::BetaNoticeInserter.new(self, response.headers),
  ]

  template_name = response.headers[Headers::TEMPLATE_HEADER] || 'wrapper'
  process(processors, body, template(template_name))
end

#template(template_name) ⇒ Object



19
20
21
22
23
24
25
26
27
# File 'lib/slimmer/skin.rb', line 19

def template(template_name)
  if use_cache
    template_cache.fetch(template_name) do
      load_template(template_name)
    end
  else
    load_template(template_name)
  end
end

#template_url(template_name) ⇒ Object



46
47
48
49
50
# File 'lib/slimmer/skin.rb', line 46

def template_url(template_name)
  host = asset_host.dup
  host += '/' unless host =~ /\/$/
  "#{host}templates/#{template_name}.html.erb"
end