4 # :title: Stream filters
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
8 # This file collects methods to handle 'stream filters', a generic mechanism
9 # to transform text+attributes into other text+attributes
14 # The DataStream class. A DataStream is just a Hash. The :text key has
15 # a special meaning because it's the value that will be used when
16 # converting to String
17 class DataStream < Hash
19 # call-seq: new(text, hash)
21 # Create a new DataStream with text _text_ and attributes held by _hash_.
22 # Either parameter can be missing; if _text_ is missing, the text can be
23 # be defined in the _hash_ with a :text key.
26 self.replace(args.pop) if Hash === args.last
27 self[:text] = args.first if args.length > 0
30 # Returns the :text key
36 # The DataFilter class. A DataFilter is a wrapper around a block
37 # that can be run on a DataStream to process it. The block is supposed to
38 # return another DataStream object
40 def initialize(&block)
41 raise "No block provided" unless block_given?
53 # filter(filter1, filter2, ..., filterN, stream) -> stream
54 # filter(filter1, filter2, ..., filterN, text, hash) -> stream
55 # filter(filter1, filter2, ..., filterN, hash) -> stream
57 # This method processes the DataStream _stream_ with the filters <i>filter1</i>,
58 # <i>filter2</i>, ..., _filterN_, in sequence (the output of each filter is used
59 # as input for the next one.
60 # _stream_ can be provided either as a DataStream or as a String and a Hash
61 # (see DataStream.new).
66 # the stream is a Hash, check if the previous element is not a Symbol
67 if Symbol === args[-2]
68 ds = DataStream.new(args.pop)
70 ds = DataStream.new(*args.slice!(-2, 2))
73 # the stream is just whatever else
74 ds = DataStream.new(args.pop)
77 return ds if names.empty?
78 # check if filters exist
79 missing = names - @filters.keys
80 raise "Missing filters: #{missing.join(', ')}" unless missing.empty?
81 fs = @filters.values_at(*names)
82 fs.inject(ds) { |mid, f| mid = f.call(mid) }
85 # This method returns the global filter name for filter _name_
87 def global_filter_name(name, group=nil)
88 (group ? "#{group}.#{name}" : name.to_s).intern
91 # This method checks if the bot has a filter named _name_ (in group
93 def has_filter?(name, group=nil)
94 @filters.key?(global_filter_name(name, group))
97 # This method checks if the bot has a filter group named _name_
98 def has_filter_group?(name)
99 @filter_group.key?(name)
102 # This method is used to register a new filter
103 def register_filter(name, group=nil, &block)
104 raise "No block provided" unless block_given?
106 tlkey = global_filter_name(name, group)
108 if has_filter?(tlkey)
109 debug "Overwriting filter #{tlkey}"
111 @filters[tlkey] = DataFilter.new(&block)
115 @filter_group[gkey] ||= {}
116 if @filter_group[gkey].key?(key)
117 debug "Overwriting filter #{key} in group #{gkey}"
119 @filter_group[gkey][key] = @filters[tlkey]
123 # This method is used to retrieve the filter names (in a given group)
124 def filter_names(group=nil)
127 return [] unless defined? @filter_group and @filter_group.key?(gkey)
128 return @filter_group[gkey].keys
130 return [] unless defined? @filters
135 # This method is used to retrieve the filter group names
137 return [] unless defined? @filter_group
138 return @filter_group.keys
141 # This method clears the filter list and installs the identity filter
149 register_filter(:identity) { |stream| stream }
154 # read accessor for the default filter group for this BotModule
156 @filter_group ||= name
159 # write accessor for the default filter group for this BotModule
160 def filter_group=(name)
164 # define a filter defaulting to the default filter group
166 def define_filter(filter, &block)
167 @bot.register_filter(filter, self.filter_group, &block)
170 # load filters associated with the BotModule by looking in
171 # the path(s) specified by the :path option, defaulting to
172 # * Config::datadir/filters/<name>.rb
173 # * botclass/filters/<name>.rb
174 # (note that as <name> we use #dirname() rather than #name(),
175 # since we're looking for datafiles; this is only relevant
176 # for the very few plugins whose dirname differs from name)
177 def load_filters(options={})
180 us = "#{self.dirname}.rb"
182 File.join(Config::datadir, 'filters', us),
183 @bot.path('filters', us)
186 paths = options[:path]
188 paths = [options[:path]]
192 instance_eval(File.read(file), file) if File.exist?(file)