Skip to content

Commit

Permalink
Add a separation option for the excerpt function
Browse files Browse the repository at this point in the history
The separation option enable to keep entire words, lines or anything.
To split by line, like github, we can set the separation option as \n.
To split by word, like google, we can set the separation option as " ".
The radius option represent the number of lines or words we want to
have in the result.
The default behaviour is the same. If we don't set the separation
option, it split the text any where.
  • Loading branch information
GCorbel committed Sep 8, 2012
1 parent f415475 commit 963c50e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 11 deletions.
2 changes: 2 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##


* Add `separation` option for `ActionView::Helpers::TextHelper.excerpt`. *Guirec Corbel*

* Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH* * Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH*


class InvoicesController < ApplicationController class InvoicesController < ApplicationController
Expand Down
60 changes: 49 additions & 11 deletions actionpack/lib/action_view/helpers/text_helper.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ def highlight(text, phrases, options = {})
# Extracts an excerpt from +text+ that matches the first instance of +phrase+. # Extracts an excerpt from +text+ that matches the first instance of +phrase+.
# The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The
# will be stripped in any case. If the +phrase+ isn't found, nil is returned. # <tt>:separator</tt> enable to choose the delimation. The resulting string will be stripped in any case. If the +phrase+
# isn't found, nil is returned.
# #
# excerpt('This is an example', 'an', :radius => 5) # excerpt('This is an example', 'an', :radius => 5)
# # => ...s is an exam... # # => ...s is an exam...
Expand All @@ -143,21 +144,37 @@ def highlight(text, phrases, options = {})
# #
# excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ') # excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
# # => <chop> is also an example # # => <chop> is also an example
#
# excerpt('This is a very beautiful morning', 'very', :separator => ' ', :radius => 1)
# # => ...a very beautiful...
def excerpt(text, phrase, options = {}) def excerpt(text, phrase, options = {})
return unless text && phrase return unless text && phrase
radius = options.fetch(:radius, 100) radius = options.fetch(:radius, 100)
omission = options.fetch(:omission, "...") omission = options.fetch(:omission, "...")
separator = options.fetch(:separator, "")

phrase = Regexp.escape(phrase)
regex = /#{phrase}/i

return unless matches = text.match(regex)
phrase = matches[0]

text.split(separator).each do |value|
if value.match(regex)
regex = phrase = value
break
end
end


phrase = Regexp.escape(phrase) first_part, second_part = text.split(regex, 2)
return unless found_pos = text =~ /(#{phrase})/i


start_pos = [ found_pos - radius, 0 ].max options = options.merge(:part_position => :first)
end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min prefix, first_part = cut_part(first_part, options)


prefix = start_pos > 0 ? omission : "" options = options.merge(:part_position => :second)
postfix = end_pos < text.length - 1 ? omission : "" postfix, second_part = cut_part(second_part, options)


prefix + text[start_pos..end_pos].strip + postfix prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
end end


# Attempts to pluralize the +singular+ word unless +count+ is 1. If # Attempts to pluralize the +singular+ word unless +count+ is 1. If
Expand Down Expand Up @@ -402,6 +419,27 @@ def split_paragraphs(text)
t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
end end
end end

def cut_part(part, options)
radius = options.fetch(:radius, 100)
omission = options.fetch(:omission, "...")
separator = options.fetch(:separator, "")
part_position = options.fetch(:part_position)

return "", "" unless part

part = part.split(separator)
part.delete("")
affix = part.size > radius ? omission : ""
part = if part_position == :first
drop_index = [part.length - radius, 0].max
part.drop(drop_index).join(separator)
else
part.first(radius).join(separator)
end

return affix, part
end
end end
end end
end end
13 changes: 13 additions & 0 deletions actionpack/test/template/text_helper_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -303,6 +303,19 @@ def test_excerpt_does_not_modify_the_options_hash
assert_equal options, passed_options assert_equal options, passed_options
end end


def test_excerpt_with_separator
options = { :separator => ' ', :radius => 1 }
assert_equal('...a very beautiful...', excerpt('This is a very beautiful morning', 'very', options))
assert_equal('This is...', excerpt('This is a very beautiful morning', 'this', options))
assert_equal('...beautiful morning', excerpt('This is a very beautiful morning', 'morning', options))

options = { :separator => "\n", :radius => 0 }
assert_equal("...very long...", excerpt("my very\nvery\nvery long\nstring", 'long', options))

options = { :separator => "\n", :radius => 1 }
assert_equal("...very\nvery long\nstring", excerpt("my very\nvery\nvery long\nstring", 'long', options))
end

def test_word_wrap def test_word_wrap
assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15)) assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
end end
Expand Down

0 comments on commit 963c50e

Please sign in to comment.