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.
- checksums.yaml +5 -13
- data/.gitignore +1 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +7 -13
- data/CHANGELOG.md +18 -0
- data/Gemfile +5 -0
- data/README.md +163 -71
- data/Rakefile +2 -2
- data/lib/meta_tags.rb +18 -0
- data/lib/meta_tags/configuration.rb +21 -0
- data/lib/meta_tags/meta_tags_collection.rb +5 -10
- data/lib/meta_tags/renderer.rb +114 -37
- data/lib/meta_tags/text_normalizer.rb +71 -6
- data/lib/meta_tags/version.rb +1 -1
- data/lib/meta_tags/view_helper.rb +12 -11
- data/meta-tags.gemspec +2 -1
- data/spec/configuration_spec.rb +14 -0
- data/spec/controller_helper_spec.rb +4 -4
- data/spec/spec_helper.rb +69 -13
- data/spec/text_normalizer/normalize_title_spec.rb +43 -0
- data/spec/text_normalizer/truncate_array_spec.rb +60 -0
- data/spec/view_helper/charset_spec.rb +16 -0
- data/spec/view_helper/custom_spec.rb +67 -0
- data/spec/view_helper/description_spec.rb +61 -0
- data/spec/view_helper/icon_spec.rb +42 -0
- data/spec/view_helper/keywords_spec.rb +58 -0
- data/spec/view_helper/links_spec.rb +125 -0
- data/spec/view_helper/module_spec.rb +41 -0
- data/spec/view_helper/noindex_spec.rb +107 -0
- data/spec/view_helper/open_graph_spec.rb +86 -0
- data/spec/view_helper/open_search_spec.rb +33 -0
- data/spec/view_helper/refresh_spec.rb +32 -0
- data/spec/view_helper/title_spec.rb +155 -0
- data/spec/view_helper/twitter_spec.rb +31 -0
- data/spec/view_helper_spec.rb +57 -0
- metadata +53 -21
- data/spec/meta_tags_spec.rb +0 -570
data/Rakefile
CHANGED
data/lib/meta_tags.rb
CHANGED
@@ -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
|
-
|
86
|
-
|
85
|
+
site_title = extract(:site) || ''
|
86
|
+
title = extract_title || []
|
87
|
+
separator = extract_separator
|
88
|
+
reverse = extract(:reverse) === true
|
87
89
|
|
88
|
-
site_title
|
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.
|
data/lib/meta_tags/renderer.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
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, :
|
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, :
|
59
|
+
tags << ContentTag.new(:title, content: title) if title.present?
|
46
60
|
end
|
47
61
|
|
48
|
-
# Renders
|
62
|
+
# Renders icon(s) tag.
|
49
63
|
#
|
50
64
|
# @param [Array<Tag>] tags a buffer object to store tag in.
|
51
65
|
#
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
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
|
63
|
-
|
64
|
-
normalized_meta_tags[
|
65
|
-
tags << Tag.new(:meta, :name
|
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, :
|
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', :
|
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
|
-
|
95
|
-
|
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, :
|
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
|
-
|
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
|
-
|
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, :
|
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 [
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
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).
|
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
|