meta-tags 2.0.0 → 2.1.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.
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