require 'cgi'
require 'bigdecimal'
module Liquid
module StandardFilters
HTML_ESCAPE = {
'&'.freeze => '&'.freeze,
'>'.freeze => '>'.freeze,
'<'.freeze => '<'.freeze,
'"'.freeze => '"'.freeze,
"'".freeze => '''.freeze
}
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
# Return the size of an array or of an string
def size(input)
input.respond_to?(:size) ? input.size : 0
end
# convert an input string to DOWNCASE
def downcase(input)
input.to_s.downcase
end
# convert an input string to UPCASE
def upcase(input)
input.to_s.upcase
end
# capitalize words in the input centence
def capitalize(input)
input.to_s.capitalize
end
def escape(input)
CGI.escapeHTML(input).untaint rescue input
end
alias_method :h, :escape
def escape_once(input)
input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
end
def url_encode(input)
CGI.escape(input) rescue input
end
def slice(input, offset, length=nil)
offset = Integer(offset)
length = length ? Integer(length) : 1
if input.is_a?(Array)
input.slice(offset, length) || []
else
input.to_s.slice(offset, length) || ''
end
end
# Truncate a string down to x characters
def truncate(input, length = 50, truncate_string = "...".freeze)
if input.nil? then return end
l = length.to_i - truncate_string.length
l = 0 if l < 0
input.length > length.to_i ? input[0...l] + truncate_string : input
end
def truncatewords(input, words = 15, truncate_string = "...".freeze)
if input.nil? then return end
wordlist = input.to_s.split
l = words.to_i - 1
l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
end
# Split input string into an array of substrings separated by given pattern.
#
# Example:
#
{{ post | split '//' | first }}
#
def split(input, pattern)
input.to_s.split(pattern)
end
def strip(input)
input.to_s.strip
end
def lstrip(input)
input.to_s.lstrip
end
def rstrip(input)
input.to_s.rstrip
end
def strip_html(input)
empty = ''.freeze
input.to_s.gsub(//m, empty).gsub(//m, empty).gsub(//m, empty).gsub(/<.*?>/m, empty)
end
# Remove all newlines from the string
def strip_newlines(input)
input.to_s.gsub(/\r?\n/, ''.freeze)
end
# Join elements of the array with certain character between them
def join(input, glue = ' '.freeze)
InputIterator.new(input).join(glue)
end
# Sort elements of the array
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = InputIterator.new(input)
if property.nil?
ary.sort
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
ary.sort {|a,b| a[property] <=> b[property] }
elsif ary.first.respond_to?(property)
ary.sort {|a,b| a.send(property) <=> b.send(property) }
end
end
# Remove duplicate elements from an array
# provide optional property with which to determine uniqueness
def uniq(input, property = nil)
ary = InputIterator.new(input)
if property.nil?
input.uniq
elsif input.first.respond_to?(:[])
input.uniq{ |a| a[property] }
end
end
# Reverse the elements of an array
def reverse(input)
ary = InputIterator.new(input)
ary.reverse
end
# map/collect on a given property
def map(input, property)
InputIterator.new(input).map do |e|
e = e.call if e.is_a?(Proc)
if property == "to_liquid".freeze
e
elsif e.respond_to?(:[])
e[property]
end
end
end
# Replace occurrences of a string with another
def replace(input, string, replacement = ''.freeze)
input.to_s.gsub(string, replacement.to_s)
end
# Replace the first occurrences of a string with another
def replace_first(input, string, replacement = ''.freeze)
input.to_s.sub(string, replacement.to_s)
end
# remove a substring
def remove(input, string)
input.to_s.gsub(string, ''.freeze)
end
# remove the first occurrences of a substring
def remove_first(input, string)
input.to_s.sub(string, ''.freeze)
end
# add one string to another
def append(input, string)
input.to_s + string.to_s
end
# prepend a string to another
def prepend(input, string)
string.to_s + input.to_s
end
# Add
tags in front of all newlines in input string
def newline_to_br(input)
input.to_s.gsub(/\n/, "
\n".freeze)
end
# Reformat a date using Ruby's core Time#strftime( string ) -> string
#
# %a - The abbreviated weekday name (``Sun'')
# %A - The full weekday name (``Sunday'')
# %b - The abbreviated month name (``Jan'')
# %B - The full month name (``January'')
# %c - The preferred local date and time representation
# %d - Day of the month (01..31)
# %H - Hour of the day, 24-hour clock (00..23)
# %I - Hour of the day, 12-hour clock (01..12)
# %j - Day of the year (001..366)
# %m - Month of the year (01..12)
# %M - Minute of the hour (00..59)
# %p - Meridian indicator (``AM'' or ``PM'')
# %s - Number of seconds since 1970-01-01 00:00:00 UTC.
# %S - Second of the minute (00..60)
# %U - Week number of the current year,
# starting with the first Sunday as the first
# day of the first week (00..53)
# %W - Week number of the current year,
# starting with the first Monday as the first
# day of the first week (00..53)
# %w - Day of the week (Sunday is 0, 0..6)
# %x - Preferred representation for the date alone, no time
# %X - Preferred representation for the time alone, no date
# %y - Year without a century (00..99)
# %Y - Year with century
# %Z - Time zone name
# %% - Literal ``%'' character
#
# See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime
def date(input, format)
return input if format.to_s.empty?
return input unless date = to_date(input)
date.strftime(format.to_s)
end
# Get the first element of the passed in array
#
# Example:
# {{ product.images | first | to_img }}
#
def first(array)
array.first if array.respond_to?(:first)
end
# Get the last element of the passed in array
#
# Example:
# {{ product.images | last | to_img }}
#
def last(array)
array.last if array.respond_to?(:last)
end
# addition
def plus(input, operand)
apply_operation(input, operand, :+)
end
# subtraction
def minus(input, operand)
apply_operation(input, operand, :-)
end
# multiplication
def times(input, operand)
apply_operation(input, operand, :*)
end
# division
def divided_by(input, operand)
apply_operation(input, operand, :/)
end
def modulo(input, operand)
apply_operation(input, operand, :%)
end
def round(input, n = 0)
result = to_number(input).round(to_number(n))
result = result.to_f if result.is_a?(BigDecimal)
result = result.to_i if n == 0
result
end
def ceil(input)
to_number(input).ceil.to_i
end
def floor(input)
to_number(input).floor.to_i
end
def default(input, default_value = "".freeze)
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
is_blank ? default_value : input
end
private
def to_number(obj)
case obj
when Float
BigDecimal.new(obj.to_s)
when Numeric
obj
when String
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
else
0
end
end
def to_date(obj)
return obj if obj.respond_to?(:strftime)
case obj
when 'now'.freeze, 'today'.freeze
Time.now
when /\A\d+\z/, Integer
Time.at(obj.to_i)
when String
Time.parse(obj)
else
nil
end
rescue ArgumentError
nil
end
def apply_operation(input, operand, operation)
result = to_number(input).send(operation, to_number(operand))
result.is_a?(BigDecimal) ? result.to_f : result
end
class InputIterator
include Enumerable
def initialize(input)
@input = if input.is_a?(Array)
input.flatten
elsif input.is_a?(Hash)
[input]
elsif input.is_a?(Enumerable)
input
else
Array(input)
end
end
def join(glue)
to_a.join(glue)
end
def reverse
reverse_each.to_a
end
def each
@input.each do |e|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end
end
end
Template.register_filter(StandardFilters)
end