Skip to content

Commit

Permalink
allow to pass multiple filters to #convert
Browse files Browse the repository at this point in the history
  • Loading branch information
inukshuk committed Apr 7, 2013
1 parent 953e13f commit fc02adc
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 72 deletions.
8 changes: 5 additions & 3 deletions lib/bibtex/bibliography.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,14 @@ def parse_months
end


# Converts all enties using the given filter. If an optional block is given
# Converts all enties using the given filter(s). If an optional block is given
# the block is used as a condition (the block will be called with each
# entry). @see Entry#convert!
def convert (filter)
def convert(*filters)
filters = filters.flatten.map { |f| Filters.resolve!(f) }

entries.each_value do |entry|
entry.convert!(filter) if !block_given? || yield(entry)
entry.convert!(*filters) if !block_given? || yield(entry)
end

self
Expand Down
15 changes: 10 additions & 5 deletions lib/bibtex/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -647,18 +647,23 @@ def values_at(*arguments)
end
end

# Returns a duplicate entry with all values converted using the filter.
# Returns a duplicate entry with all values converted using the filter(s).
# If an optional block is given, only those values will be converted where
# the block returns true (the block will be called with each key-value pair).
#
# @see #convert!
def convert(filter)
block_given? ? dup.convert!(filter, &Proc.new) : dup.convert!(filter)
def convert(*filters)
block_given? ? dup.convert!(*filters, &Proc.new) : dup.convert!(*filters)
end

# In-place variant of @see #convert
def convert!(filter)
fields.each_pair { |k,v| !block_given? || yield(k,v) ? v.convert!(filter) : v }
def convert!(*filters)
filters = filters.flatten.map { |f| Filters.resolve!(f) }

fields.each_pair do |k, v|
(!block_given? || yield(k, v)) ? v.convert!(*filters) : v
end

self
end

Expand Down
18 changes: 11 additions & 7 deletions lib/bibtex/filters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,40 @@
module BibTeX
class Filter
include Singleton

class << self
# Hook called by Ruby if Filter is subclassed
def inherited(base)
base.class_eval { include Singleton }
subclasses << base
end

# Returns a list of all current Filters
def subclasses
@subclasses ||= []
end
end

def apply(value)
value
end

alias convert apply
alias << apply

end

module Filters
LOAD_PATH = [File.expand_path('..', __FILE__), 'filters'].join('/').freeze

Dir.glob("#{LOAD_PATH}/*.rb").each do |filter|
require filter
end

def self.resolve!(filter)
resolve(filter) || raise(ArgumentError, "Failed to load filter #{filter.inspect}")
end

def self.resolve(filter)
case
when filter.respond_to?(:apply)
Expand All @@ -44,7 +48,7 @@ def self.resolve(filter)
klass && klass.instance
else
nil
end
end
end
end
end
Expand Down
20 changes: 12 additions & 8 deletions lib/bibtex/names.rb
Original file line number Diff line number Diff line change
Expand Up @@ -260,17 +260,21 @@ def to_xml
end
end

def convert(filter)
dup.convert!(filter)
def convert(*filters)
dup.convert!(*filters)
end

def convert!(filter)
if f = Filters.resolve(filter)
each_pair { |k,v| self[k] = f.apply(v) unless v.nil? }
else
raise ArgumentError, "Failed to load filter #{filter.inspect}"
end
def convert!(*filters)
filters.flatten.each do |filter|

f = Filters.resolve(filter) ||
raise(ArgumentError, "Failed to load filter #{filter.inspect}")

each_pair do |k, v|
self[k] = f.apply(v) unless v.nil?
end
end

self
end

Expand Down
85 changes: 42 additions & 43 deletions lib/bibtex/value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
#--
# BibTeX-Ruby
# Copyright (C) 2010-2012 Sylvester Keil <sylvester.keil.or.at>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
Expand Down Expand Up @@ -48,10 +48,10 @@ module BibTeX
class Value
extend Forwardable
include Comparable

attr_reader :tokens
alias to_a tokens

def_delegators :to_s, :=~, :===, *String.instance_methods(false).reject { |m| m =~ /^\W|^length$|^dup$|!$/ }
def_delegators :@tokens, :[], :length
def_delegator :@tokens, :each, :each_token
Expand All @@ -65,27 +65,27 @@ class Value
def self.create(*args)
args[0].class < Value && args.size == 1 ? args[0].dup : Value.new(args)
end

def initialize(*arguments)
@tokens = []
arguments.flatten.compact.each do |argument|
add(argument)
end
end

def initialize_copy(other)
@tokens = other.tokens.dup
end

def merge(other)
dup.merge!(other)
end

def merge!(other)
other.tokens.each do |token|
add token unless include_token?(token)
end

self
end

Expand All @@ -110,10 +110,10 @@ def add(argument)
end
self
end

alias << add
alias push add

[:strip!, :upcase!, :downcase!, :sub!, :gsub!, :chop!, :chomp!, :rstrip!].each do |method_id|
define_method(method_id) do |*arguments, &block|
tokens.each do |part|
Expand All @@ -122,7 +122,7 @@ def add(argument)
self
end
end

def replace(*arguments)
return self unless has_symbol?
arguments.flatten.each do |argument|
Expand Down Expand Up @@ -157,7 +157,7 @@ def join(separator = '')
end
self
end

# call-seq:
# Value.new('foo').to_s #=> "foo"
# Value.new(:foo).to_s #=> "foo"
Expand Down Expand Up @@ -189,79 +189,78 @@ def to_s(options = {})
def value
atomic? ? @tokens[0] : @tokens.map { |v| v.is_a?(::String) ? v.inspect : v }.join(' # ')
end

alias :v :value

def inspect
"#<#{self.class} #{tokens.map(&:inspect).join(', ')}>"
end

# Returns true if the Value is empty or consists of a single token.
def atomic?
@tokens.length < 2
end
# Returns true if the value is a BibTeX name value.

# Returns true if the value is a BibTeX name value.
def name?; false; end

alias :names? :name?

def to_name
Names.parse(to_s)
end

alias to_names to_name

# Returns true if the Value's content is a date.
# Returns true if the Value's content is a date.
def date?
!to_date.nil?
end

# Returns the string as a date.
def to_date
require 'date'
Date.parse(to_s)
rescue
nil
end

# Returns true if the Value's content is numeric.
def numeric?
to_s =~ /^\s*[+-]?\d+[\/\.]?\d*\s*$/
end

def to_citeproc (options = {})
to_s(options)
end

# Returns true if the Value contains at least one symbol.
def symbol?
tokens.detect { |v| v.is_a?(Symbol) }
end

alias has_symbol? symbol?

# Returns all symbols contained in the Value.
def symbols
tokens.select { |v| v.is_a?(Symbol) }
end
# Returns a new Value with all string values converted according to the given filter.
def convert (filter)
dup.convert!(filter)

# Returns a new Value with all string values converted according to the given filter(s).
def convert (*filters)
dup.convert!(*filters)
end

# Converts all string values according to the given filter.
def convert! (filter)
if f = Filters.resolve(filter)

# Converts all string values according to the given filter(s).
def convert! (*filters)
filters.flatten.each do |filter|
f = Filters.resolve!(filter)
tokens.map! { |t| f.apply(t) }
else
raise ArgumentError, "Failed to load filter #{filter.inspect}"
end

self
end

def method_missing (name, *args)
case
when name.to_s =~ /^(?:convert|from)_([a-z]+)(!)?$/
Expand All @@ -270,15 +269,15 @@ def method_missing (name, *args)
super
end
end

def respond_to? (method)
method =~ /^(?:convert|from)_([a-z]+)(!)?$/ || super
end

def <=> (other)
to_s <=> other.to_s
end

end

end
21 changes: 15 additions & 6 deletions test/bibtex/test_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,21 +269,30 @@ class EntryTest < MiniTest::Spec
assert_equal 'Melville', e['author'][0]['family']
end

describe 'given a filter' do
describe 'given a filter object or a filter name' do
before do
@filter = Object.new
def @filter.apply (value); value.is_a?(::String) ? value.upcase : value; end

class SuffixB < BibTeX::Filter
def apply(value)
value.is_a?(::String) ? "#{value}b" : value
end
end
end

it 'supports arbitrary conversion' do
e = @entry.convert(@filter)
assert_equal 'MOBY DICK', e.title
assert_equal 'Moby Dick', @entry.title
@entry.convert(@filter).title.must_equal 'MOBY DICK'
@entry.convert(:suffixb).title.must_equal 'Moby Dickb'
end

it 'supports multiple filters' do
@entry.convert(@filter, :suffixb).title.must_equal 'MOBY DICKb'
@entry.convert(:suffixb, @filter).title.must_equal 'MOBY DICKB'
end

it 'supports arbitrary in-place conversion' do
@entry.convert!(@filter)
assert_equal 'MOBY DICK', @entry.title
@entry.convert!(@filter).title.must_equal 'MOBY DICK'
end

it 'supports conditional arbitrary in-place conversion' do
Expand Down
Loading

0 comments on commit fc02adc

Please sign in to comment.