twitter-text 1.4.17 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- == twitter-text
1
+ == twitter-text {<img src="https://secure.travis-ci.org/twitter/twitter-text-rb.png" />}[http://travis-ci.org/twitter/twitter-text-rb]
2
2
 
3
3
  A gem that provides text processing routines for Twitter Tweets. The major
4
4
  reason for this is to unify the various auto-linking and extraction of
@@ -107,16 +107,6 @@ Thanks to everybody who has filed issues, provided feedback or contributed patch
107
107
 
108
108
  === Copyright and License
109
109
 
110
- Copyright 2011 Twitter, Inc.
110
+ Copyright 2011 Twitter, Inc.
111
111
 
112
- Licensed under the Apache License, Version 2.0 (the "License");
113
- you may not use this work except in compliance with the License.
114
- You may obtain a copy of the License in the LICENSE file, or at:
115
-
116
- http://www.apache.org/licenses/LICENSE-2.0
117
-
118
- Unless required by applicable law or agreed to in writing, software
119
- distributed under the License is distributed on an "AS IS" BASIS,
120
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
121
- See the License for the specific language governing permissions and
122
- limitations under the License.
112
+ Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env rake
2
2
  require 'bundler'
3
+ include Rake::DSL
3
4
  Bundler::GemHelper.install_tasks
4
5
 
5
6
  task :default => ['spec', 'test:conformance']
@@ -0,0 +1,436 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'set'
4
+
5
+ module Twitter
6
+ # A module for including Tweet auto-linking in a class. The primary use of this is for helpers/views so they can auto-link
7
+ # usernames, lists, hashtags and URLs.
8
+ module Autolink extend self
9
+ # Default CSS class for auto-linked lists
10
+ DEFAULT_LIST_CLASS = "tweet-url list-slug".freeze
11
+ # Default CSS class for auto-linked usernames
12
+ DEFAULT_USERNAME_CLASS = "tweet-url username".freeze
13
+ # Default CSS class for auto-linked hashtags
14
+ DEFAULT_HASHTAG_CLASS = "tweet-url hashtag".freeze
15
+ # Default CSS class for auto-linked cashtags
16
+ DEFAULT_CASHTAG_CLASS = "tweet-url cashtag".freeze
17
+
18
+ # Default URL base for auto-linked usernames
19
+ DEFAULT_USERNAME_URL_BASE = "https://twitter.com/".freeze
20
+ # Default URL base for auto-linked lists
21
+ DEFAULT_LIST_URL_BASE = "https://twitter.com/".freeze
22
+ # Default URL base for auto-linked hashtags
23
+ DEFAULT_HASHTAG_URL_BASE = "https://twitter.com/#!/search?q=%23".freeze
24
+ # Default URL base for auto-linked cashtags
25
+ DEFAULT_CASHTAG_URL_BASE = "https://twitter.com/#!/search?q=%24".freeze
26
+
27
+ # Default attributes for invisible span tag
28
+ DEFAULT_INVISIBLE_TAG_ATTRS = "style='position:absolute;left:-9999px;'".freeze
29
+
30
+ DEFAULT_OPTIONS = {
31
+ :list_class => DEFAULT_LIST_CLASS,
32
+ :username_class => DEFAULT_USERNAME_CLASS,
33
+ :hashtag_class => DEFAULT_HASHTAG_CLASS,
34
+ :cashtag_class => DEFAULT_CASHTAG_CLASS,
35
+
36
+ :username_url_base => DEFAULT_USERNAME_URL_BASE,
37
+ :list_url_base => DEFAULT_LIST_URL_BASE,
38
+ :hashtag_url_base => DEFAULT_HASHTAG_URL_BASE,
39
+ :cashtag_url_base => DEFAULT_CASHTAG_URL_BASE,
40
+
41
+ :invisible_tag_attrs => DEFAULT_INVISIBLE_TAG_ATTRS
42
+ }.freeze
43
+
44
+ def auto_link_with_json(text, json, options = {})
45
+ # concatenate entities
46
+ entities = json.values().flatten()
47
+
48
+ # map JSON entity to twitter-text entity
49
+ entities.each do |entity|
50
+ entity.symbolize_keys!
51
+ # hashtag
52
+ entity[:hashtag] = entity[:text] if entity[:text]
53
+ end
54
+
55
+ auto_link_entities(text, entities, options)
56
+ end
57
+
58
+ def auto_link_entities(text, entities, options = {}, &block)
59
+ return text if entities.empty?
60
+
61
+ # NOTE deprecate these attributes not options keys in options hash, then use html_attrs
62
+ options = DEFAULT_OPTIONS.merge(options)
63
+ options[:html_attrs] = extract_html_attrs_from_options!(options)
64
+ options[:html_attrs][:rel] ||= "nofollow" unless options[:suppress_no_follow]
65
+
66
+ Twitter::Rewriter.rewrite_entities(text, entities) do |entity, chars|
67
+ if entity[:url]
68
+ link_to_url(entity, chars, options, &block)
69
+ elsif entity[:hashtag]
70
+ link_to_hashtag(entity, chars, options, &block)
71
+ elsif entity[:screen_name]
72
+ link_to_screen_name(entity, chars, options, &block)
73
+ elsif entity[:cashtag]
74
+ link_to_cashtag(entity, chars, options, &block)
75
+ end
76
+ end
77
+ end
78
+
79
+ # Add <tt><a></a></tt> tags around the usernames, lists, hashtags and URLs in the provided <tt>text</tt>.
80
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash:
81
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
82
+ # and place in the <tt><a></tt> tag.
83
+ #
84
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
85
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
86
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
87
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
88
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
89
+ # <tt>:username_url_base</tt>:: the value for <tt>href</tt> attribute on username links. The <tt>@username</tt> (minus the <tt>@</tt>) will be appended at the end of this.
90
+ # <tt>:list_url_base</tt>:: the value for <tt>href</tt> attribute on list links. The <tt>@username/list</tt> (minus the <tt>@</tt>) will be appended at the end of this.
91
+ # <tt>:hashtag_url_base</tt>:: the value for <tt>href</tt> attribute on hashtag links. The <tt>#hashtag</tt> (minus the <tt>#</tt>) will be appended at the end of this.
92
+ # <tt>:cashtag_url_base</tt>:: the value for <tt>href</tt> attribute on cashtag links. The <tt>$cashtag</tt> (minus the <tt>$</tt>) will be appended at the end of this.
93
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
94
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
95
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
96
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
97
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
98
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
99
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
100
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
101
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
102
+ def auto_link(text, options = {}, &block)
103
+ auto_link_entities(text, Extractor.extract_entities_with_indices(text, :extract_url_without_protocol => false), options, &block)
104
+ end
105
+
106
+ # Add <tt><a></a></tt> tags around the usernames and lists in the provided <tt>text</tt>. The
107
+ # <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
108
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
109
+ # and place in the <tt><a></tt> tag.
110
+ #
111
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
112
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
113
+ # <tt>:username_url_base</tt>:: the value for <tt>href</tt> attribute on username links. The <tt>@username</tt> (minus the <tt>@</tt>) will be appended at the end of this.
114
+ # <tt>:list_url_base</tt>:: the value for <tt>href</tt> attribute on list links. The <tt>@username/list</tt> (minus the <tt>@</tt>) will be appended at the end of this.
115
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
116
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
117
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
118
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
119
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
120
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
121
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
122
+ def auto_link_usernames_or_lists(text, options = {}, &block) # :yields: list_or_username
123
+ auto_link_entities(text, Extractor.extract_mentions_or_lists_with_indices(text), options, &block)
124
+ end
125
+
126
+ # Add <tt><a></a></tt> tags around the hashtags in the provided <tt>text</tt>.
127
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
128
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
129
+ # and place in the <tt><a></tt> tag.
130
+ #
131
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
132
+ # <tt>:hashtag_url_base</tt>:: the value for <tt>href</tt> attribute. The hashtag text (minus the <tt>#</tt>) will be appended at the end of this.
133
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
134
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
135
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
136
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
137
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
138
+ def auto_link_hashtags(text, options = {}, &block) # :yields: hashtag_text
139
+ auto_link_entities(text, Extractor.extract_hashtags_with_indices(text), options, &block)
140
+ end
141
+
142
+ # Add <tt><a></a></tt> tags around the cashtags in the provided <tt>text</tt>.
143
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
144
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
145
+ # and place in the <tt><a></tt> tag.
146
+ #
147
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
148
+ # <tt>:cashtag_url_base</tt>:: the value for <tt>href</tt> attribute. The cashtag text (minus the <tt>$</tt>) will be appended at the end of this.
149
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
150
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
151
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
152
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
153
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
154
+ def auto_link_cashtags(text, options = {}, &block) # :yields: cashtag_text
155
+ auto_link_entities(text, Extractor.extract_cashtags_with_indices(text), options, &block)
156
+ end
157
+
158
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
159
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
160
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
161
+ # and place in the <tt><a></tt> tag.
162
+ #
163
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
164
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
165
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
166
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
167
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
168
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
169
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
170
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
171
+ def auto_link_urls(text, options = {}, &block)
172
+ auto_link_entities(text, Extractor.extract_urls_with_indices(text, :extract_url_without_protocol => false), options, &block)
173
+ end
174
+
175
+ # These methods are deprecated, will be removed in future.
176
+ extend Deprecation
177
+
178
+ # <b>Deprecated</b>: Please use auto_link_urls instead.
179
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
180
+ # Any elements in the <tt>href_options</tt> hash will be converted to HTML attributes
181
+ # and place in the <tt><a></tt> tag.
182
+ # Unless <tt>href_options</tt> contains <tt>:suppress_no_follow</tt>
183
+ # the <tt>rel="nofollow"</tt> attribute will be added.
184
+ alias :auto_link_urls_custom :auto_link_urls
185
+ deprecate :auto_link_urls_custom, :auto_link_urls
186
+
187
+ private
188
+
189
+ HTML_ENTITIES = {
190
+ '&' => '&amp;',
191
+ '>' => '&gt;',
192
+ '<' => '&lt;',
193
+ '"' => '&quot;',
194
+ "'" => '&#39;'
195
+ }
196
+
197
+ def html_escape(text)
198
+ text && text.to_s.gsub(/[&"'><]/) do |character|
199
+ HTML_ENTITIES[character]
200
+ end
201
+ end
202
+
203
+ # NOTE We will make this private in future.
204
+ public :html_escape
205
+
206
+ # Options which should not be passed as HTML attributes
207
+ OPTIONS_NOT_ATTRIBUTES = Set.new([
208
+ :url_class, :list_class, :username_class, :hashtag_class, :cashtag_class,
209
+ :username_url_base, :list_url_base, :hashtag_url_base, :cashtag_url_base,
210
+ :username_url_block, :list_url_block, :hashtag_url_block, :link_url_block,
211
+ :username_include_symbol, :suppress_lists, :suppress_no_follow, :url_entities,
212
+ :invisible_tag_attrs, :symbol_tag, :text_with_symbol_tag, :url_target,
213
+ :link_attribute_block, :link_text_block
214
+ ]).freeze
215
+
216
+ def extract_html_attrs_from_options!(options)
217
+ html_attrs = {}
218
+ options.reject! do |key, value|
219
+ unless OPTIONS_NOT_ATTRIBUTES.include?(key)
220
+ html_attrs[key] = value
221
+ true
222
+ end
223
+ end
224
+ html_attrs
225
+ end
226
+
227
+ def url_entities_hash(url_entities)
228
+ (url_entities || {}).inject({}) do |entities, entity|
229
+ entity = entity.symbolize_keys
230
+ entities[entity[:url]] = entity
231
+ entities
232
+ end
233
+ end
234
+
235
+ def link_to_url(entity, chars, options = {})
236
+ url = entity[:url]
237
+
238
+ href = if options[:link_url_block]
239
+ options[:link_url_block].call(url)
240
+ else
241
+ url
242
+ end
243
+
244
+ # NOTE auto link to urls do not use any default values and options
245
+ # like url_class but use suppress_no_follow.
246
+ html_attrs = options[:html_attrs].dup
247
+ html_attrs[:class] = options[:url_class] if options.key?(:url_class)
248
+
249
+ # add target attribute only if :url_target is specified
250
+ html_attrs[:target] = options[:url_target] if options.key?(:url_target)
251
+
252
+ url_entities = url_entities_hash(options[:url_entities])
253
+
254
+ # use entity from urlEntities if available
255
+ url_entity = url_entities[url] || entity
256
+ link_text = if url_entity[:display_url]
257
+ html_attrs[:title] ||= url_entity[:expanded_url]
258
+ link_url_with_entity(url_entity, options)
259
+ else
260
+ html_escape(url)
261
+ end
262
+
263
+ link_to_text(entity, link_text, href, html_attrs, options)
264
+ end
265
+
266
+ def link_url_with_entity(entity, options)
267
+ display_url = entity[:display_url]
268
+ expanded_url = entity[:expanded_url]
269
+ invisible_tag_attrs = options[:invisible_tag_attrs] || DEFAULT_INVISIBLE_TAG_ATTRS
270
+
271
+ # Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
272
+ # should contain the full original URL (expanded_url), not the display URL.
273
+ #
274
+ # Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
275
+ # font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
276
+ # Elements with font-size:0 get copied even though they are not visible.
277
+ # Note that display:none doesn't work here. Elements with display:none don't get copied.
278
+ #
279
+ # Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
280
+ # wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
281
+ # everything with the tco-ellipsis class.
282
+ #
283
+ # Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1
284
+ # For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
285
+ # For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
286
+ display_url_sans_ellipses = display_url.gsub("…", "")
287
+
288
+ if expanded_url.include?(display_url_sans_ellipses)
289
+ before_display_url, after_display_url = expanded_url.split(display_url_sans_ellipses, 2)
290
+ preceding_ellipsis = /\A…/.match(display_url).to_s
291
+ following_ellipsis = /…\z/.match(display_url).to_s
292
+
293
+ # As an example: The user tweets "hi http://longdomainname.com/foo"
294
+ # This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
295
+ # This will get rendered as:
296
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
297
+ # …
298
+ # <!-- There's a chance the onCopy event handler might not fire. In case that happens,
299
+ # we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
300
+ # The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
301
+ # fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
302
+ # e.g. "hi http://longdomainname.com/foo".
303
+ # <span style='font-size:0'>&nbsp;</span>
304
+ # </span>
305
+ # <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
306
+ # http://longdomai
307
+ # </span>
308
+ # <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
309
+ # nname.com/foo
310
+ # </span>
311
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
312
+ # <span style='font-size:0'>&nbsp;</span>
313
+ # …
314
+ # </span>
315
+ %(<span class="tco-ellipsis">#{preceding_ellipsis}<span #{invisible_tag_attrs}>&nbsp;</span></span>) <<
316
+ %(<span #{invisible_tag_attrs}>#{html_escape(before_display_url)}</span>) <<
317
+ %(<span class="js-display-url">#{html_escape(display_url_sans_ellipses)}</span>) <<
318
+ %(<span #{invisible_tag_attrs}>#{html_escape(after_display_url)}</span>) <<
319
+ %(<span class="tco-ellipsis"><span #{invisible_tag_attrs}>&nbsp;</span>#{following_ellipsis}</span>)
320
+ else
321
+ html_escape(display_url)
322
+ end
323
+ end
324
+
325
+ def link_to_hashtag(entity, chars, options = {})
326
+ hash = chars[entity[:indices].first]
327
+ hashtag = entity[:hashtag]
328
+ hashtag = yield(hashtag) if block_given?
329
+
330
+ href = if options[:hashtag_url_block]
331
+ options[:hashtag_url_block].call(hashtag)
332
+ else
333
+ "#{options[:hashtag_url_base]}#{hashtag}"
334
+ end
335
+
336
+ html_attrs = {
337
+ :class => "#{options[:hashtag_class]}",
338
+ # FIXME As our conformance test, hash in title should be half-width,
339
+ # this should be bug of conformance data.
340
+ :title => "##{hashtag}"
341
+ }.merge(options[:html_attrs])
342
+
343
+ link_to_text_with_symbol(entity, hash, hashtag, href, html_attrs, options)
344
+ end
345
+
346
+ def link_to_cashtag(entity, chars, options = {})
347
+ dollar = chars[entity[:indices].first]
348
+ cashtag = entity[:cashtag]
349
+ cashtag = yield(cashtag) if block_given?
350
+
351
+ href = if options[:cashtag_url_block]
352
+ options[:cashtag_url_block].call(cashtag)
353
+ else
354
+ "#{options[:cashtag_url_base]}#{cashtag}"
355
+ end
356
+
357
+ html_attrs = {
358
+ :class => "#{options[:cashtag_class]}",
359
+ :title => "$#{cashtag}"
360
+ }.merge(options[:html_attrs])
361
+
362
+ link_to_text_with_symbol(entity, dollar, cashtag, href, html_attrs, options)
363
+ end
364
+
365
+ def link_to_screen_name(entity, chars, options = {})
366
+ name = "#{entity[:screen_name]}#{entity[:list_slug]}"
367
+ chunk = name
368
+ chunk = yield(name) if block_given?
369
+ name.downcase!
370
+
371
+ at = chars[entity[:indices].first]
372
+
373
+ html_attrs = options[:html_attrs].dup
374
+
375
+ if entity[:list_slug] && !entity[:list_slug].empty? && !options[:suppress_lists]
376
+ href = if options[:list_url_block]
377
+ options[:list_url_block].call(name)
378
+ else
379
+ "#{options[:list_url_base]}#{name}"
380
+ end
381
+ html_attrs[:class] ||= "#{options[:list_class]}"
382
+ else
383
+ href = if options[:username_url_block]
384
+ options[:username_url_block].call(chunk)
385
+ else
386
+ "#{options[:username_url_base]}#{name}"
387
+ end
388
+ html_attrs[:class] ||= "#{options[:username_class]}"
389
+ end
390
+
391
+ link_to_text_with_symbol(entity, at, chunk, href, html_attrs, options)
392
+ end
393
+
394
+ def link_to_text_with_symbol(entity, symbol, text, href, attributes = {}, options = {})
395
+ tagged_symbol = options[:symbol_tag] ? "<#{options[:symbol_tag]}>#{symbol}</#{options[:symbol_tag]}>" : symbol
396
+ text = html_escape(text)
397
+ tagged_text = options[:text_with_symbol_tag] ? "<#{options[:text_with_symbol_tag]}>#{text}</#{options[:text_with_symbol_tag]}>" : text
398
+ if options[:username_include_symbol] || symbol !~ Twitter::Regex::REGEXEN[:at_signs]
399
+ "#{link_to_text(entity, tagged_symbol + tagged_text, href, attributes, options)}"
400
+ else
401
+ "#{tagged_symbol}#{link_to_text(entity, tagged_text, href, attributes, options)}"
402
+ end
403
+ end
404
+
405
+ def link_to_text(entity, text, href, attributes = {}, options = {})
406
+ attributes[:href] = href
407
+ options[:link_attribute_block].call(entity, attributes) if options[:link_attribute_block]
408
+ text = options[:link_text_block].call(entity, text) if options[:link_text_block]
409
+ %(<a#{tag_attrs(attributes)}>#{text}</a>)
410
+ end
411
+
412
+ BOOLEAN_ATTRIBUTES = Set.new([:disabled, :readonly, :multiple, :checked]).freeze
413
+
414
+ def tag_attrs(attributes)
415
+ attributes.keys.sort_by{|k| k.to_s}.inject("") do |attrs, key|
416
+ value = attributes[key]
417
+
418
+ if BOOLEAN_ATTRIBUTES.include?(key)
419
+ value = value ? key : nil
420
+ end
421
+
422
+ unless value.nil?
423
+ value = case value
424
+ when Array
425
+ value.compact.join(" ")
426
+ else
427
+ value
428
+ end
429
+ attrs << %( #{html_escape(key)}="#{html_escape(value)}")
430
+ end
431
+
432
+ attrs
433
+ end
434
+ end
435
+ end
436
+ end
@@ -0,0 +1,15 @@
1
+ module Twitter
2
+ module Deprecation
3
+ def deprecate(method, new_method = nil)
4
+ deprecated_method = :"deprecated_#{method}"
5
+ message = "Deprecation: `#{method}` is deprecated."
6
+ message << " Please use `#{new_method}` instead." if new_method
7
+
8
+ alias_method(deprecated_method, method)
9
+ define_method method do |*args, &block|
10
+ warn message
11
+ send(deprecated_method, *args, &block)
12
+ end
13
+ end
14
+ end
15
+ end