meta-tags 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -4,8 +4,8 @@ Bundler::GemHelper.install_tasks
4
4
  require 'rspec/core/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
 
7
- task :test => :spec
8
- task :default => :spec
7
+ task test: :spec
8
+ task default: :spec
9
9
 
10
10
  desc 'Starts irb with MetaTags gem loaded'
11
11
  task :console do
@@ -3,10 +3,28 @@ require 'action_view'
3
3
 
4
4
  # MetaTags gem namespace.
5
5
  module MetaTags
6
+ # Returns MetaTags gem configuration.
7
+ #
8
+ def self.config
9
+ @@config ||= Configuration.new
10
+ end
11
+
12
+ # Configures MetaTags gem.
13
+ #
14
+ # @yield [Configuration] configuration object.
15
+ # @example
16
+ #
17
+ # MetaTags.configure do |config|
18
+ # # config.title_limit = 100
19
+ # end
20
+ def self.configure
21
+ yield config
22
+ end
6
23
  end
7
24
 
8
25
  require 'meta_tags/version'
9
26
 
27
+ require 'meta_tags/configuration'
10
28
  require 'meta_tags/controller_helper'
11
29
  require 'meta_tags/meta_tags_collection'
12
30
  require 'meta_tags/renderer'
@@ -0,0 +1,21 @@
1
+ module MetaTags
2
+ # MetaTags configuration.
3
+ class Configuration
4
+ # How many characters to truncate title to.
5
+ attr_accessor :title_limit
6
+ # How many characters to truncate description to.
7
+ attr_accessor :description_limit
8
+ # How many characters to truncate keywords to.
9
+ attr_accessor :keywords_limit
10
+ # Keywords separator - a string to join keywords with.
11
+ attr_accessor :keywords_separator
12
+
13
+ # Initializes a new instance of Configuration class.
14
+ def initialize
15
+ @title_limit = 70
16
+ @description_limit = 160
17
+ @keywords_limit = 255
18
+ @keywords_separator = ', '
19
+ end
20
+ end
21
+ end
@@ -82,17 +82,12 @@ module MetaTags
82
82
  # @return [String] page title.
83
83
  #
84
84
  def extract_full_title
85
- title = extract_title || []
86
- separator = extract_separator
85
+ site_title = extract(:site) || ''
86
+ title = extract_title || []
87
+ separator = extract_separator
88
+ reverse = extract(:reverse) === true
87
89
 
88
- site_title = extract(:site).presence
89
- title.unshift(site_title) if site_title
90
- title = TextNormalizer.normalize_title(title)
91
-
92
- title.reverse! if extract(:reverse) === true
93
- title = TextNormalizer.safe_join(title, separator)
94
-
95
- title
90
+ TextNormalizer.normalize_title(site_title, title, separator, reverse)
96
91
  end
97
92
 
98
93
  # Extracts page title as an array of segments without site title and separators.
@@ -18,15 +18,18 @@ module MetaTags
18
18
  def render(view)
19
19
  tags = []
20
20
 
21
+ render_charset(tags)
21
22
  render_title(tags)
22
- render_description(tags)
23
- render_keywords(tags)
23
+ render_icon(tags)
24
+ render_with_normalization(tags, :description)
25
+ render_with_normalization(tags, :keywords)
24
26
  render_refresh(tags)
25
27
  render_noindex(tags)
26
28
  render_alternate(tags)
29
+ render_open_search(tags)
27
30
  render_links(tags)
28
31
 
29
- render_hash(tags, :twitter, :name_key => :name)
32
+ render_hash(tags, :og, name_key: :property)
30
33
  render_hashes(tags)
31
34
  render_custom(tags)
32
35
 
@@ -35,6 +38,17 @@ module MetaTags
35
38
 
36
39
  protected
37
40
 
41
+
42
+ # Renders charset tag.
43
+ #
44
+ # @param [Array<Tag>] tags a buffer object to store tag in.
45
+ #
46
+ def render_charset(tags)
47
+ if charset = meta_tags.extract(:charset)
48
+ tags << Tag.new(:meta, charset: charset) if charset.present?
49
+ end
50
+ end
51
+
38
52
  # Renders title tag.
39
53
  #
40
54
  # @param [Array<Tag>] tags a buffer object to store tag in.
@@ -42,27 +56,37 @@ module MetaTags
42
56
  def render_title(tags)
43
57
  title = meta_tags.extract_full_title
44
58
  normalized_meta_tags[:title] = title
45
- tags << ContentTag.new(:title, :content => title) if title.present?
59
+ tags << ContentTag.new(:title, content: title) if title.present?
46
60
  end
47
61
 
48
- # Renders description meta tag.
62
+ # Renders icon(s) tag.
49
63
  #
50
64
  # @param [Array<Tag>] tags a buffer object to store tag in.
51
65
  #
52
- def render_description(tags)
53
- description = TextNormalizer.normalize_description(meta_tags.extract(:description))
54
- normalized_meta_tags[:description] = description
55
- tags << Tag.new(:meta, :name => :description, :content => description) if description.present?
66
+ def render_icon(tags)
67
+ if icon = meta_tags.extract(:icon)
68
+ if String === icon
69
+ tags << Tag.new(:link, rel: 'icon', href: icon, type: 'image/x-icon')
70
+ else
71
+ icon = [icon] if Hash === icon
72
+ icon.each do |icon_params|
73
+ icon_params = { rel: 'icon', type: 'image/x-icon' }.with_indifferent_access.merge(icon_params)
74
+ tags << Tag.new(:link, icon_params)
75
+ end
76
+ end
77
+ end
56
78
  end
57
79
 
58
- # Renders keywords meta tag.
80
+ # Renders meta tag with normalization (should have a corresponding normalize_
81
+ # method in TextNormalizer).
59
82
  #
60
83
  # @param [Array<Tag>] tags a buffer object to store tag in.
84
+ # @see TextNormalizer
61
85
  #
62
- def render_keywords(tags)
63
- keywords = TextNormalizer.normalize_keywords(meta_tags.extract(:keywords))
64
- normalized_meta_tags[:keywords] = keywords
65
- tags << Tag.new(:meta, :name => :keywords, :content => keywords) if keywords.present?
86
+ def render_with_normalization(tags, name)
87
+ value = TextNormalizer.public_send("normalize_#{name}", meta_tags.extract(name))
88
+ normalized_meta_tags[name] = value
89
+ tags << Tag.new(:meta, name: name, content: value) if value.present?
66
90
  end
67
91
 
68
92
  # Renders noindex and nofollow meta tags.
@@ -71,7 +95,7 @@ module MetaTags
71
95
  #
72
96
  def render_noindex(tags)
73
97
  meta_tags.extract_noindex.each do |name, content|
74
- tags << Tag.new(:meta, :name => name, :content => content) if content.present?
98
+ tags << Tag.new(:meta, name: name, content: content) if content.present?
75
99
  end
76
100
  end
77
101
 
@@ -81,7 +105,7 @@ module MetaTags
81
105
  #
82
106
  def render_refresh(tags)
83
107
  if refresh = meta_tags.extract(:refresh)
84
- tags << Tag.new(:meta, 'http-equiv' => 'refresh', :content => refresh.to_s) if refresh.present?
108
+ tags << Tag.new(:meta, 'http-equiv' => 'refresh', content: refresh.to_s) if refresh.present?
85
109
  end
86
110
  end
87
111
 
@@ -91,8 +115,29 @@ module MetaTags
91
115
  #
92
116
  def render_alternate(tags)
93
117
  if alternate = meta_tags.extract(:alternate)
94
- alternate.each do |hreflang, href|
95
- tags << Tag.new(:link, :rel => 'alternate', :href => href, :hreflang => hreflang) if href.present?
118
+ if Hash === alternate
119
+ alternate.each do |hreflang, href|
120
+ tags << Tag.new(:link, rel: 'alternate', href: href, hreflang: hreflang) if href.present?
121
+ end
122
+ elsif Array === alternate
123
+ alternate.each do |link_params|
124
+ tags << Tag.new(:link, { rel: 'alternate' }.with_indifferent_access.merge(link_params))
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # Renders open_search link tag.
131
+ #
132
+ # @param [Array<Tag>] tags a buffer object to store tag in.
133
+ #
134
+ def render_open_search(tags)
135
+ if open_search = meta_tags.extract(:open_search)
136
+ href = open_search[:href]
137
+ title = open_search[:title]
138
+ if href.present?
139
+ type = "application/opensearchdescription+xml"
140
+ tags << Tag.new(:link, rel: 'search', type: type, href: href, title: title)
96
141
  end
97
142
  end
98
143
  end
@@ -106,7 +151,7 @@ module MetaTags
106
151
  href = meta_tags.extract(tag_name)
107
152
  if href.present?
108
153
  @normalized_meta_tags[tag_name] = href
109
- tags << Tag.new(:link, :rel => tag_name, :href => href)
154
+ tags << Tag.new(:link, rel: tag_name, href: href)
110
155
  end
111
156
  end
112
157
  end
@@ -118,7 +163,7 @@ module MetaTags
118
163
  def render_hashes(tags, options = {})
119
164
  meta_tags.meta_tags.each do |property, data|
120
165
  if data.is_a?(Hash)
121
- process_tree(tags, property, data)
166
+ process_hash(tags, property, data, options)
122
167
  meta_tags.extract(property)
123
168
  end
124
169
  end
@@ -131,7 +176,7 @@ module MetaTags
131
176
  def render_hash(tags, key, options = {})
132
177
  data = meta_tags.meta_tags[key]
133
178
  if data.is_a?(Hash)
134
- process_tree(tags, key, data, options)
179
+ process_hash(tags, key, data, options)
135
180
  meta_tags.extract(key)
136
181
  end
137
182
  end
@@ -143,7 +188,7 @@ module MetaTags
143
188
  def render_custom(tags)
144
189
  meta_tags.meta_tags.each do |name, data|
145
190
  Array(data).each do |val|
146
- tags << Tag.new(:meta, :name => name, :content => val)
191
+ tags << Tag.new(:meta, name: name, content: val)
147
192
  end
148
193
  meta_tags.extract(name)
149
194
  end
@@ -152,25 +197,57 @@ module MetaTags
152
197
  # Recursive method to process all the hashes and arrays on meta tags
153
198
  #
154
199
  # @param [Array<Tag>] tags a buffer object to store tag in.
155
- # @param [Hash, String] property a Hash or a String to render as meta tag.
156
- # @param [String, Symbol] content text content or a symbol reference to
200
+ # @param [String, Symbol] property a Hash or a String to render as meta tag.
201
+ # @param [Hash, Array, String, Symbol] content text content or a symbol reference to
157
202
  # top-level meta tag.
158
203
  #
159
204
  def process_tree(tags, property, content, options = {})
160
- content = [content] if content.is_a?(Hash)
161
- Array(content).each do |c|
162
- if c.is_a?(Hash)
163
- c.each do |key, value|
164
- key = key.to_s == '_' ? property : "#{property}:#{key}"
165
- value = normalized_meta_tags[value] if value.is_a?(Symbol)
166
- process_tree(tags, key, value, options)
167
- end
168
- else
169
- name_key = options.fetch(:name_key, :property)
170
- value_key = options.fetch(:value_key, :content)
171
- tags << Tag.new(:meta, name_key => property.to_s, value_key => c) unless c.blank?
172
- end
205
+ method = case content
206
+ when Hash
207
+ :process_hash
208
+ when Array
209
+ :process_array
210
+ else
211
+ :render_tag
212
+ end
213
+ send(method, tags, property, content, options)
214
+ end
215
+
216
+ # Recursive method to process a hash with meta tags
217
+ #
218
+ # @param [Array<Tag>] tags a buffer object to store tag in.
219
+ # @param [String, Symbol] property a Hash or a String to render as meta tag.
220
+ # @param [Hash] content nested meta tag attributes.
221
+ #
222
+ def process_hash(tags, property, content, options = {})
223
+ content.each do |key, value|
224
+ key = key.to_s == '_' ? property : "#{property}:#{key}"
225
+ value = normalized_meta_tags[value] if value.is_a?(Symbol)
226
+ process_tree(tags, key, value, options)
173
227
  end
174
228
  end
229
+
230
+ # Recursive method to process a hash with meta tags
231
+ #
232
+ # @param [Array<Tag>] tags a buffer object to store tag in.
233
+ # @param [String, Symbol] property a Hash or a String to render as meta tag.
234
+ # @param [Array] content array of nested meta tag attributes or values.
235
+ #
236
+ def process_array(tags, property, content, options = {})
237
+ content.each { |v| process_tree(tags, property, v, options) }
238
+ end
239
+
240
+ # Recursive method to process a hash with meta tags
241
+ #
242
+ # @param [Array<Tag>] tags a buffer object to store tag in.
243
+ # @param [String, Symbol] name a Hash or a String to render as meta tag.
244
+ # @param [String, Symbol] value text content or a symbol reference to
245
+ # top-level meta tag.
246
+ #
247
+ def render_tag(tags, name, value, options = {})
248
+ name_key = options.fetch(:name_key, :name)
249
+ value_key = options.fetch(:value_key, :content)
250
+ tags << Tag.new(:meta, name_key => name.to_s, value_key => value) unless value.blank?
251
+ end
175
252
  end
176
253
  end
@@ -3,11 +3,32 @@ module MetaTags
3
3
  module TextNormalizer
4
4
  # Normalize title value.
5
5
  #
6
+ # @param [String] site_title site title.
6
7
  # @param [String, Array<String>] title title string.
8
+ # @param [String] separator a string to join title parts with.
9
+ # @param [true,false] reverse whether title should be reversed.
7
10
  # @return [Array<String>] array of title parts with tags removed.
8
11
  #
9
- def self.normalize_title(title)
10
- Array(title).flatten.map(&method(:strip_tags))
12
+ def self.normalize_title(site_title, title, separator, reverse = false)
13
+ title = Array(title).flatten.map(&method(:strip_tags))
14
+ title.reject!(&:blank?)
15
+ site_title = strip_tags(site_title)
16
+ separator = strip_tags(separator)
17
+
18
+ if MetaTags.config.title_limit
19
+ limit = MetaTags.config.title_limit - separator.length
20
+ if limit > site_title.length
21
+ title = truncate_array(title, limit - site_title.length, separator)
22
+ else
23
+ site_title = truncate(site_title, limit)
24
+ # Site title is too long, we have to skip page title
25
+ title = []
26
+ end
27
+ end
28
+
29
+ title.unshift(site_title) if site_title.present?
30
+ title.reverse! if reverse
31
+ safe_join(title, separator)
11
32
  end
12
33
 
13
34
  # Normalize description value.
@@ -18,7 +39,8 @@ module MetaTags
18
39
  #
19
40
  def self.normalize_description(description)
20
41
  return '' if description.blank?
21
- helpers.truncate(cleanup_string(description), :length => 200)
42
+ description = cleanup_string(description)
43
+ truncate(description, MetaTags.config.description_limit)
22
44
  end
23
45
 
24
46
  # Normalize keywords value.
@@ -28,7 +50,11 @@ module MetaTags
28
50
  #
29
51
  def self.normalize_keywords(keywords)
30
52
  return '' if keywords.blank?
31
- cleanup_strings(keywords).join(', ').downcase
53
+ keywords = cleanup_strings(keywords).each(&:downcase!)
54
+ separator = strip_tags MetaTags.config.keywords_separator
55
+
56
+ keywords = truncate_array(keywords, MetaTags.config.keywords_limit, separator)
57
+ safe_join(keywords, separator)
32
58
  end
33
59
 
34
60
  # Easy way to get access to Rails helpers.
@@ -45,7 +71,7 @@ module MetaTags
45
71
  # @return [String] string with no HTML tags.
46
72
  #
47
73
  def self.strip_tags(string)
48
- helpers.strip_tags(string)
74
+ ERB::Util.html_escape helpers.strip_tags(string)
49
75
  end
50
76
 
51
77
  # This method returns a html safe string similar to what <tt>Array#join</tt>
@@ -68,7 +94,7 @@ module MetaTags
68
94
  # space characters squashed into a single space.
69
95
  #
70
96
  def self.cleanup_string(string)
71
- strip_tags(string).gsub(/\s+/, ' ').strip
97
+ strip_tags(string).gsub(/\s+/, ' ').strip.html_safe
72
98
  end
73
99
 
74
100
  # Cleans multiple strings up.
@@ -76,8 +102,47 @@ module MetaTags
76
102
  # @param [Array<String>] strings input strings.
77
103
  # @return [Array<String>] clean strings.
78
104
  # @see cleanup_string
105
+ #
79
106
  def self.cleanup_strings(strings)
80
107
  Array(strings).flatten.map(&method(:cleanup_string))
81
108
  end
109
+
110
+ # Truncates a string to a specific limit.
111
+ #
112
+ # @param [String] string input strings.
113
+ # @param [Integer,nil] limit characters number to truncate to.
114
+ # @param [String] natural_separator natural separator to truncate at.
115
+ # @return [String] truncated string.
116
+ #
117
+ def self.truncate(string, limit = nil, natural_separator = ' ')
118
+ string = helpers.truncate(string, length: limit, separator: natural_separator, omission: '') if limit
119
+ string
120
+ end
121
+
122
+ # Truncates a string to a specific limit.
123
+ #
124
+ # @param [Array<String>] string_array input strings.
125
+ # @param [Integer,nil] limit characters number to truncate to.
126
+ # @param [String] separator separator that will be used to join array later.
127
+ # @param [String] natural_separator natural separator to truncate at.
128
+ # @return [String] truncated string.
129
+ #
130
+ def self.truncate_array(string_array, limit = nil, separator = '', natural_separator = ' ')
131
+ return string_array if limit.nil? || limit == 0
132
+ length = 0
133
+ result = []
134
+ string_array.each do |string|
135
+ limit_left = limit - length - (result.any? ? separator.length : 0)
136
+ if string.length > limit_left
137
+ result << truncate(string, limit_left, natural_separator)
138
+ break
139
+ end
140
+ length += (result.any? ? separator.length : 0) + string.length
141
+ result << string
142
+ # No more strings will fit
143
+ break if length + separator.length >= limit
144
+ end
145
+ result
146
+ end
82
147
  end
83
148
  end